From aa65bd8c0e64e4c3b294777b0df94a509d179d6c Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 3 Nov 2026 22:50:10 -0500 Subject: [PATCH 01/29] CHANGELOG: the route record's failure-cost entry names no product The entry for version 2 of the route record named the product whose proposal it adopted, against CONTRIBUTING's public-text rule. The name is redacted in place with a dated marker, as this changelog's history redacts names, and the sentence keeps its disclosure: the proposal came from a product of WAI's maintainers [name removed 2026-11-03]. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index 6a64d746d..dea0c8f33 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -672,7 +672,8 @@ and no `depends_on :macos`. It now has both, so the tap carries it as the releas unknown: the expected figures are `null` and rank after every measured one, by the existing rule for `null`, never as no failures. The record restates every basis and expected figure. The job never pays for failures: the unit-price and spend limits read the list price and cost, and a line bills the billed attempt. This is the failure - cost waiTransport proposed, with its physics and its basis, except that an unknown failure is not counted as zero. + cost a product of WAI's maintainers [name removed 2026-11-03] proposed, with its physics and its basis, except that + an unknown failure is not counted as zero. - **A hop receipt is bound to its output** (§5.2, §6): in version 2 the attempt that served states `outputs`, the content hashes (jwp-receipts §2.1) of what it delivered in order, and its OER/2 receipt's `subject.output` must be their commitment, or the record is refused **`receipt_output_mismatch`**: a receipt cannot be moved to vouch for an -- 2.54.0 (Apple Git-157) From 779b203c35ab6663110497edf37f14988b91d342 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 3 Nov 2026 22:50:15 -0500 Subject: [PATCH 02/29] check-public-names: the products WAI's maintainers sell are on the list The name check read only the private list, which never held the maintainers' own product names, so a changelog line naming one passed it. Those names are public already, and the private list also serves checks of text about the products, which names them where it should; so they are listed in the script, MAINTAINER_PRODUCTS, in any case, joined or with one hyphen or underscore, the shorter one standing alone. They are applied beside the private list, with the same opt-out, and an unset private list still fails closed. The self-test gains a case for each spelling, a generic phrase that is not a hit, an honoured opt-out, and the unset list. Co-Authored-By: Claude Opus 5.5 --- scripts/check-public-names.sh | 22 +++++++++++++++++++--- scripts/test-public-text-checks.sh | 20 ++++++++++++++++++-- wai/CONTRIBUTING.md | 7 ++++--- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/scripts/check-public-names.sh b/scripts/check-public-names.sh index dcb52b8a6..c291788a2 100755 --- a/scripts/check-public-names.sh +++ b/scripts/check-public-names.sh @@ -6,8 +6,8 @@ # customer or commercial product. Standards, standards bodies, protocols, # papers and open-source projects may be named, factually. # -# The patterns are NOT in this repository. They are read from the -# environment variable WAI_NAME_DENYLIST: newline-separated POSIX extended +# The patterns for other parties' names are NOT in this repository. They are +# read from the environment variable WAI_NAME_DENYLIST: newline-separated POSIX extended # regular expressions, matched case-sensitively (write `[Ff]oo` where both # cases matter). In CI the variable comes from a forge Actions secret of the # same name; locally, export it from a private file before committing: @@ -16,6 +16,13 @@ # # A public list would itself publish the names it exists to keep out. # +# The names of the products WAI's maintainers sell are in this script, as +# MAINTAINER_PRODUCTS below. They are public already, so listing them here +# publishes nothing; and the private list cannot hold them, because it also +# serves the checks of text that is about those products. The script requires +# the private list as before, and applies both lists together, with the same +# opt-out. +# # Scope: the tracked files under wai/ with the extensions .md .rs .toml .py # .sh .mjs .js .json .html .css .ttl .h .c .txt, and wai/'s .gitignore files; # or, when files are given as arguments, those files (the self-test, @@ -50,6 +57,14 @@ if [ -z "${WAI_NAME_DENYLIST:-}" ]; then exit 2 fi +# The products of WAI's maintainers, in any case, joined or with one hyphen or +# underscore (a crate or a namespace spells them so). The shorter name must +# stand alone, so that a run of letters inside an encoded string is not a hit. +MAINTAINER_PRODUCTS=( + '[Ww][Aa][Ii][-_]?[Tt][Rr][Aa][Nn][Ss][Pp][Oo][Rr][Tt]' + '(^|[^[:alnum:]])[Ww][Aa][Ii][-_]?[Oo][Ss]([^[:alnum:]]|$)' +) + patterns="$(mktemp)" out="$(mktemp)" trap 'rm -f "$patterns" "$out"' EXIT @@ -58,6 +73,7 @@ if [ ! -s "$patterns" ]; then echo "WAI_NAME_DENYLIST holds no pattern" >&2 exit 2 fi +printf '%s\n' "${MAINTAINER_PRODUCTS[@]}" >> "$patterns" # Compile every pattern once before scanning. grep exits 1 when nothing matches # and 2 on an error; only 0 and 1 mean the patterns are usable. grep's message # is suppressed: it could quote a private pattern. @@ -122,7 +138,7 @@ while IFS= read -r line; do fi # Print the location only: the matched text may be a name the list keeps private. if [ $hits -eq 0 ]; then - echo "::error::public text names a company, vendor, customer or product (patterns: WAI_NAME_DENYLIST)" + echo "::error::public text names a company, vendor, customer or product (patterns: WAI_NAME_DENYLIST and MAINTAINER_PRODUCTS)" fi echo "${line%%:*}:$(printf '%s' "${line#*:}" | cut -d: -f1)" hits=$((hits + 1)) diff --git a/scripts/test-public-text-checks.sh b/scripts/test-public-text-checks.sh index a2c8b0bea..a148fd10f 100755 --- a/scripts/test-public-text-checks.sh +++ b/scripts/test-public-text-checks.sh @@ -9,8 +9,10 @@ # - an opt-out with a reason is honoured, and one with an empty reason (the # comment form ``, whose `-->` must not count as the # reason) is not; -# - a pattern grep cannot compile exits 2, never "clean". -# The fixture names a made-up company, never a real one. +# - a pattern grep cannot compile exits 2, never "clean"; +# - the products of WAI's maintainers are hits beside any private list. +# The fixtures name a made-up company, never a real one, and the maintainers' +# products, which check-public-names.sh lists itself. # # Exit status: 0 when every case gives its stated status, 1 otherwise. @@ -76,6 +78,20 @@ expect 1 "names: a hit among many relative paths is a hit" in_dir "$tmp" env WAI expect 0 "names: a missing file among many fails closed and is named" names_file "gone.md" in_dir "$tmp" env WAI_NAME_DENYLIST="$list" bash "$names" "${many[@]}" sub/gone.md expect 0 "names: a directory given as a file fails closed and is named" names_file "/sub" in_dir "$tmp" env WAI_NAME_DENYLIST="$list" bash "$names" "${many[@]}" sub +printf 'The failure cost waiTransport proposed.\n' > "$tmp/product-joined.md" +printf 'A sidecar under waitransport.provisional.route.\n' > "$tmp/product-lower.md" +printf 'A crate named wai_transport.\n' > "$tmp/product-underscore.md" +printf 'It runs on waiOS.\n' > "$tmp/product-short.md" +printf 'The WAI transport layer, WAI OS integration and xWAIOSy.\n' > "$tmp/product-generic.md" +printf 'waiTransport \n' > "$tmp/product-reasoned.md" +expect 1 "names: a maintainer's product is a hit beside an unrelated private list" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-joined.md" +expect 1 "names: a maintainer's product in lower case is a hit" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-lower.md" +expect 1 "names: a maintainer's product joined by an underscore is a hit" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-underscore.md" +expect 1 "names: the shorter product name standing alone is a hit" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-short.md" +expect 0 "names: the generic words, and the shorter name inside a word, are not hits" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-generic.md" +expect 0 "names: an opt-out with a reason is honoured for a product" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-reasoned.md" +expect 2 "names: with the products listed, an unset private list still fails closed" env -u WAI_NAME_DENYLIST bash "$names" "$tmp/product-joined.md" + printf 'Its reproducibility is the superiority claim.\n' > "$tmp/superiority.md" printf 'About 8 kbps, well below Xcodec at the same quality.\n' > "$tmp/well-below.md" printf 'Its token rate is among the lowest of any codec.\n' > "$tmp/among.md" diff --git a/wai/CONTRIBUTING.md b/wai/CONTRIBUTING.md index f1c2fd26f..12e22619d 100644 --- a/wai/CONTRIBUTING.md +++ b/wai/CONTRIBUTING.md @@ -107,9 +107,10 @@ Three checks enforce this. `scripts/check-no-vanity-metrics.sh` (opt-out marker `allow-phrase: `, for an identifier or a quotation) run on every change. `scripts/check-public-names.sh` reads its patterns from a private list (the `WAI_NAME_DENYLIST` secret in CI, given only to the run on a push to main, so a -pull request is checked when it lands); a functional identifier — a capability string, -a model-hub id a tool defaults to, a repository URL, an OS API name, a security -advisory id — is kept and its line marked `allow-name: `. Put the marker +pull request is checked when it lands), and holds the names of the products +WAI's maintainers sell in the script itself, since they are public already. A +functional identifier — a capability string, a model-hub id a tool defaults to, +a repository URL, an OS API name, a security advisory id — is kept and its line marked `allow-name: `. Put the marker in a comment of the file's own syntax (``, `// ...`, `# ...`); the reason must contain a letter or a digit. JSON has no comments, so a JSON file carries no such identifier: the codec glossary titles a format by its own name, -- 2.54.0 (Apple Git-157) From 98ee3e74b96f0a5e219d37db76f10fed4003e5d0 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 3 Nov 2026 23:01:44 -0500 Subject: [PATCH 03/29] =?UTF-8?q?sink-energy=20=C2=A74.6:=20outside=20meas?= =?UTF-8?q?urements=20of=20decode=20energy,=20each=20cited=20by=20arXiv=20?= =?UTF-8?q?identifier=20and=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A hardware decoder against software decoders of the same format (arXiv 2402.09001, Table IV), fewer frames chosen by the encoder (arXiv 2603.18305, Table V) and a lower resolution or coarser quantiser chosen by the encoder (arXiv 2511.00707, Table 4), each with what was measured and against what. They are informative: no record states them, no rule reads them, and none is a figure of a WAI decoder. The section says how they bear on the text: the two encoder choices cost frames or quality, SPEC §4's energy order already ranks the platform's power-efficient decode first, and a hardware row of this revision reads no media engine, so it and the first point are not like for like. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/sink-energy.md | 61 +++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index c3e2e31f6..2ad8ffa28 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -24,16 +24,17 @@ > Texts read for this revision: RFC 8032 (Ed25519); RFC 8785 (JSON Canonicalization > Scheme); RFC 8259; FIPS 180-4 (SHA-256); JCGM 100:2008 (GUM) §4.3.7 and F.2.2.1; > Commission Delegated Regulation (EU) 2019/2013, for the on-mode power a display's -> energy label states. A section number in this text is this extension's unless it names -> another document. +> energy label states; arXiv 2402.09001, 2603.18305 and 2511.00707, for §4.6's +> outside measurements of decode energy. A section number in this text is this +> extension's unless it names another document. ## 1. Scope In scope: the three records, their signatures and identities (§3); the decode table, the -procedure that produces it, and how each row's status and figure follow from its rounds -(§4); the segment receipt and its four methods (§5); the session receipt and its per-hop -split (§6); verification (§7); the reasons (§8); and what a conforming implementation -publishes (§9). +procedure that produces it, how each row's status and figure follow from its rounds, and +outside measurements of decode energy (§4); the segment receipt and its four methods +(§5); the session receipt and its per-hop split (§6); verification (§7); the reasons +(§8); and what a conforming implementation publishes (§9). Out of scope: a transport; how a counter works, which energy-measurement §9 states per platform; the energy of networks, which no sink can read and this extension never @@ -272,6 +273,54 @@ every segment that is not unmetered, is `Partial` in this revision, and a `Whole refused (`table_row_figure`, `segment_method_class`); `Whole` is kept for a profile a later revision registers. +### 4.6 Outside measurements of decode energy (informative) + +The three points below are outside measurements of decode energy. Their figures are not +figures of a WAI decoder or of a reference sink: no record of this extension states them, +and no rule of it reads them. Each is cited by its arXiv identifier and the table it is +read from, with what was measured and against what. + +1. **A platform's hardware decoder, against software decoders of the same format.** + arXiv 2402.09001, Table IV. Over AVC, VP9, HEVC and AV1, each in a random-access and a + low-delay configuration, a hardware decoder spent from 0.89% to 8.86% of the energy of + a software decoder of the same format, and from 4.44% to 8.86% of the energy of the + optimised software decoders with vector instructions enabled. The figures are dynamic + energy: the energy measured at idle is subtracted. The software decoders ran + single-threaded on a desktop processor, read by the processor's own energy counter; + the hardware decoders ran on a single-board computer's video unit, read at its power + input by an external meter. The point sets two decode paths side by side as two + machines built them. +2. **Fewer frames, chosen by the encoder.** arXiv 2603.18305, Table V, its row for all + sequences. Decoding energy was 17.60% lower, encoding energy 17.46% lower and the + bitrate 3.38% lower, each a Bjøntegaard delta at equal matched PSNR (mPSNR, computed + against the source), against encoding each sequence at its source's 120 frames per + second. A trained classifier picks, per sequence and per quantisation setting, the + frame rate the encoder codes at, the source's or a lower one; where it is lower, the + decoder decodes fewer frames than the source has. One software encoder and one + software decoder of HEVC were measured, by the processor's own energy counter, idle + subtracted. +3. **Lower resolution or coarser quantisation, chosen by the encoder.** arXiv 2511.00707, + Table 4, its row ρ = 0.05. Decoding energy was 53.54% lower and encoding energy 51.22% + lower, for an average loss of 1.68 VMAF points and 3.67 dB PSNR, against the + configuration chosen with no loss allowed (ρ = 0). Per sequence, the encoder takes, of + the resolutions and quantisers whose predicted VMAF is within 5% of the best predicted, + the one of least predicted energy. One software encoder and one software decoder of VVC + were measured on UHD sources at 60 frames per second, by the processor's own energy + counter read through a software tool. + +How they bear on this extension: + +- Points 2 and 3 are choices of what to encode, and each costs frames or picture quality. + Point 1 is a choice of decoder for the same stream. None of the three changes what a + decoder computes from a given stream. +- SPEC §4's *Energy order* already ranks first a classical decode that the sink's platform + answers supported, smooth and power-efficient. Point 1 measures what a hardware decode + spent beside a software one; the platform's answer stays a hint that orders, never an + energy figure or evidence of hardware (SPEC §4). +- A `hardware` row of this revision reads no media engine (§4.5, *Coverage*): its figure + is the processors' part of a fixed-function decode. It and point 1 measure different + things, and are not set against each other as like for like. + ## 5. The segment receipt A segment receipt is a sink's statement of what one segment of one stream cost it to -- 2.54.0 (Apple Git-157) From b8bbb4036117b6906ccccca8367dcde43655c895 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 4 Nov 2026 02:46:14 -0500 Subject: [PATCH 04/29] check-public-names: the maintainers' product names are checked on every pull request The private list is a secret, given only to the run on a push to main, so a pull request that named one of the maintainers' products was caught only after it landed. Those names need no secret: --maintainer-products-only applies MAINTAINER_PRODUCTS alone and reads no private list, and the repository checks run it on every change. The default still requires the private list and fails closed without it. The script's header and CONTRIBUTING now say which products the script lists: those the maintainers' public site names. A product not yet announced is named on no site, so the private list holds it until the day it is announced, when it moves to the script. The self-test gains cases for the new mode: a product is a hit, a clean file is clean, the private list is not read, and a missing file fails closed. Two paragraphs are rewrapped to their file's width. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/repo-checks.yml | 5 +++ scripts/check-public-names.sh | 61 +++++++++++++++++++----------- scripts/test-public-text-checks.sh | 7 +++- wai/CHANGELOG.md | 6 +++ wai/CONTRIBUTING.md | 26 +++++++------ 5 files changed, 70 insertions(+), 35 deletions(-) diff --git a/.github/workflows/repo-checks.yml b/.github/workflows/repo-checks.yml index c1a71cd09..3fd6a9476 100644 --- a/.github/workflows/repo-checks.yml +++ b/.github/workflows/repo-checks.yml @@ -108,6 +108,11 @@ jobs: - name: "public text: WAI's public text names no comparison" if: ${{ !cancelled() }} run: bash scripts/check-comparative-prose.sh + # The maintainers' product names are public, held in the script itself: this run + # reads no secret, so it checks every change, a pull request included. + - name: "public text: WAI's public text names no product of its maintainers" + if: ${{ !cancelled() }} + run: bash scripts/check-public-names.sh --maintainer-products-only # The pattern list is a secret, never given to pull-request code: the check runs # on the push to main. - name: "public text: WAI's public text names no company or product (push to main)" diff --git a/scripts/check-public-names.sh b/scripts/check-public-names.sh index c291788a2..7415e6936 100755 --- a/scripts/check-public-names.sh +++ b/scripts/check-public-names.sh @@ -7,21 +7,28 @@ # papers and open-source projects may be named, factually. # # The patterns for other parties' names are NOT in this repository. They are -# read from the environment variable WAI_NAME_DENYLIST: newline-separated POSIX extended -# regular expressions, matched case-sensitively (write `[Ff]oo` where both -# cases matter). In CI the variable comes from a forge Actions secret of the -# same name; locally, export it from a private file before committing: +# read from the environment variable WAI_NAME_DENYLIST: newline-separated +# POSIX extended regular expressions, matched case-sensitively (write `[Ff]oo` +# where both cases matter). In CI the variable comes from a forge Actions +# secret of the same name; locally, export it from a private file before +# committing: # # WAI_NAME_DENYLIST="$(cat /path/to/private/patterns)" scripts/check-public-names.sh # # A public list would itself publish the names it exists to keep out. # -# The names of the products WAI's maintainers sell are in this script, as -# MAINTAINER_PRODUCTS below. They are public already, so listing them here -# publishes nothing; and the private list cannot hold them, because it also -# serves the checks of text that is about those products. The script requires -# the private list as before, and applies both lists together, with the same -# opt-out. +# The products of WAI's maintainers that their public site names +# (waiTransport, waiOS) are in this script, as MAINTAINER_PRODUCTS below. They +# are public already, so listing them here publishes nothing. The private list +# cannot hold them: it also checks the text of the product sites, which name +# them. A product the maintainers have not announced is named on no site, so +# the private list holds it until the day it is announced, when it moves here. +# The script requires the private list as before, and applies both lists +# together, with the same opt-out. +# +# With --maintainer-products-only as its first argument, the script applies +# MAINTAINER_PRODUCTS alone and reads no private list, so it needs no secret; +# CI runs it so on every change, a pull request included. # # Scope: the tracked files under wai/ with the extensions .md .rs .toml .py # .sh .mjs .js .json .html .css .ttl .h .c .txt, and wai/'s .gitignore files; @@ -39,10 +46,10 @@ # a digit. Prose around such an identifier describes the standard or the # function, not the owner. # -# Exit status: 0 clean; 1 a hit; 2 WAI_NAME_DENYLIST unset or empty, a pattern -# grep cannot compile, a file given that is not a readable regular file (each -# such file is named), or any other grep error. The check never reads an error -# as "clean". +# Exit status: 0 clean; 1 a hit; 2 WAI_NAME_DENYLIST unset or empty (unless +# --maintainer-products-only is given), a pattern grep cannot compile, a file +# given that is not a readable regular file (each such file is named), or any +# other grep error. The check never reads an error as "clean". set -euo pipefail @@ -52,7 +59,15 @@ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CALLER_DIR="$(pwd)" cd "$REPO_ROOT" -if [ -z "${WAI_NAME_DENYLIST:-}" ]; then +products_only=0 +lists="WAI_NAME_DENYLIST and MAINTAINER_PRODUCTS" +if [ "${1:-}" = "--maintainer-products-only" ]; then + products_only=1 + lists="MAINTAINER_PRODUCTS" + shift +fi + +if [ $products_only -eq 0 ] && [ -z "${WAI_NAME_DENYLIST:-}" ]; then echo "WAI_NAME_DENYLIST is not set: nothing to check against (see the header of $0)" >&2 exit 2 fi @@ -68,10 +83,12 @@ MAINTAINER_PRODUCTS=( patterns="$(mktemp)" out="$(mktemp)" trap 'rm -f "$patterns" "$out"' EXIT -printf '%s\n' "$WAI_NAME_DENYLIST" | grep -v '^[[:space:]]*$' > "$patterns" || true -if [ ! -s "$patterns" ]; then - echo "WAI_NAME_DENYLIST holds no pattern" >&2 - exit 2 +if [ $products_only -eq 0 ]; then + printf '%s\n' "$WAI_NAME_DENYLIST" | grep -v '^[[:space:]]*$' > "$patterns" || true + if [ ! -s "$patterns" ]; then + echo "WAI_NAME_DENYLIST holds no pattern" >&2 + exit 2 + fi fi printf '%s\n' "${MAINTAINER_PRODUCTS[@]}" >> "$patterns" # Compile every pattern once before scanning. grep exits 1 when nothing matches @@ -82,7 +99,7 @@ grep -E -f "$patterns" /dev/null >/dev/null 2>&1 rc=$? set -e if [ $rc -gt 1 ]; then - echo "::error::WAI_NAME_DENYLIST holds a pattern grep cannot compile (grep exit $rc); nothing was checked" >&2 + echo "::error::a pattern grep cannot compile is among $lists (grep exit $rc); nothing was checked" >&2 exit 2 fi @@ -138,7 +155,7 @@ while IFS= read -r line; do fi # Print the location only: the matched text may be a name the list keeps private. if [ $hits -eq 0 ]; then - echo "::error::public text names a company, vendor, customer or product (patterns: WAI_NAME_DENYLIST and MAINTAINER_PRODUCTS)" + echo "::error::public text names a company, vendor, customer or product (patterns: $lists)" fi echo "${line%%:*}:$(printf '%s' "${line#*:}" | cut -d: -f1)" hits=$((hits + 1)) @@ -149,4 +166,4 @@ if [ $hits -gt 0 ]; then echo "for a functional identifier, mark the line with allow-name: ." exit 1 fi -echo "public-name check: clean (${#files[@]} files)" +echo "public-name check ($lists): clean (${#files[@]} files)" diff --git a/scripts/test-public-text-checks.sh b/scripts/test-public-text-checks.sh index a148fd10f..0d856bcf2 100755 --- a/scripts/test-public-text-checks.sh +++ b/scripts/test-public-text-checks.sh @@ -10,7 +10,8 @@ # comment form ``, whose `-->` must not count as the # reason) is not; # - a pattern grep cannot compile exits 2, never "clean"; -# - the products of WAI's maintainers are hits beside any private list. +# - the products of WAI's maintainers are hits beside any private list, and +# alone with --maintainer-products-only, which reads no private list. # The fixtures name a made-up company, never a real one, and the maintainers' # products, which check-public-names.sh lists itself. # @@ -91,6 +92,10 @@ expect 1 "names: the shorter product name standing alone is a hit" env WAI_NAME_ expect 0 "names: the generic words, and the shorter name inside a word, are not hits" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-generic.md" expect 0 "names: an opt-out with a reason is honoured for a product" env WAI_NAME_DENYLIST="$list" bash "$names" "$tmp/product-reasoned.md" expect 2 "names: with the products listed, an unset private list still fails closed" env -u WAI_NAME_DENYLIST bash "$names" "$tmp/product-joined.md" +expect 1 "names: products only, a product is a hit with no private list" env -u WAI_NAME_DENYLIST bash "$names" --maintainer-products-only "$tmp/product-joined.md" +expect 0 "names: products only, a clean file is clean with no private list" env -u WAI_NAME_DENYLIST bash "$names" --maintainer-products-only "$tmp/clean.md" +expect 0 "names: products only, the private list is not read" env WAI_NAME_DENYLIST="$list" bash "$names" --maintainer-products-only "$tmp/bare.md" +expect 2 "names: products only, a missing file fails closed" env -u WAI_NAME_DENYLIST bash "$names" --maintainer-products-only "$tmp/no-such-file.md" printf 'Its reproducibility is the superiority claim.\n' > "$tmp/superiority.md" printf 'About 8 kbps, well below Xcodec at the same quality.\n' > "$tmp/well-below.md" diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index dea0c8f33..a57e82118 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,12 @@ wire format may change and such changes are recorded here. ## Unreleased +### CI: the maintainers' product names are checked on every pull request + +- **CI:** the public-name check applies the maintainers' product names, held in the script, on every change, a pull + request included (`--maintainer-products-only`); the private list still runs on the push to main. CONTRIBUTING + states both. + ### Contribution links: a packet taken after its sequence number's drop point is late, whatever the timer did - **[live-contribution](extensions/live-contribution.md) §6.1:** a packet *arrives* when the receiver takes it. An diff --git a/wai/CONTRIBUTING.md b/wai/CONTRIBUTING.md index 12e22619d..6e84b2b25 100644 --- a/wai/CONTRIBUTING.md +++ b/wai/CONTRIBUTING.md @@ -105,18 +105,20 @@ the CHANGELOG state what WAI does, plainly: Three checks enforce this. `scripts/check-no-vanity-metrics.sh` (opt-out marker `allow-vanity: `) and `scripts/check-comparative-prose.sh` (opt-out `allow-phrase: `, for an identifier or a quotation) run on every change. -`scripts/check-public-names.sh` reads its patterns from a private list (the -`WAI_NAME_DENYLIST` secret in CI, given only to the run on a push to main, so a -pull request is checked when it lands), and holds the names of the products -WAI's maintainers sell in the script itself, since they are public already. A -functional identifier — a capability string, a model-hub id a tool defaults to, -a repository URL, an OS API name, a security advisory id — is kept and its line marked `allow-name: `. Put the marker -in a comment of the file's own syntax (``, `// ...`, `# ...`); the -reason must contain a letter or a digit. JSON has no comments, so a JSON file -carries no such identifier: the codec glossary titles a format by its own name, -FourCC, file extension or designation, and never by a company, a product or a -person ([`glossary/SCHEMA.md`](glossary/SCHEMA.md)). The checks fail closed: a -pattern that does not compile, or any grep error, fails them, and +`scripts/check-public-names.sh` holds the names of the maintainers' products +that their public site names in the script itself, since they are public +already, and checks them on every change (`--maintainer-products-only`). Its +other patterns are a private list (the `WAI_NAME_DENYLIST` secret in CI, given +only to the run on a push to main, so a pull request is checked against that +list when it lands). A functional identifier — a capability string, a model-hub +id a tool defaults to, a repository URL, an OS API name, a security advisory +id — is kept and its line marked `allow-name: `. Put the marker in a +comment of the file's own syntax (``, `// ...`, `# ...`); the reason +must contain a letter or a digit. JSON has no comments, so a JSON file carries +no such identifier: the codec glossary titles a format by its own name, FourCC, +file extension or designation, and never by a company, a product or a person +([`glossary/SCHEMA.md`](glossary/SCHEMA.md)). The checks fail closed: a pattern +that does not compile, or any grep error, fails them, and `scripts/test-public-text-checks.sh` proves that on every run. Dated research records are not rewritten: a correction is a dated annotation -- 2.54.0 (Apple Git-157) From 98f9792c3476bcae219df55439746df51949f154 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 4 Nov 2026 02:46:14 -0500 Subject: [PATCH 05/29] =?UTF-8?q?sink-energy=20=C2=A74.6:=20what=20the=20t?= =?UTF-8?q?hird=20point's=20tool=20read,=20and=20what=20its=20paper=20leav?= =?UTF-8?q?es=20unstated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Point 3 said its energy was read by the processor's own counter through a software tool. The paper's tool reads the processor's counter and the graphics processor's, and the paper states neither whether the idle energy is subtracted nor whether Table 4's savings are the measured or the predicted energy of the configurations chosen; the point now says so. Point 2's classifier chooses per rate-factor setting, of which Table V covers four, not per quantisation setting. Co-Authored-By: Claude Opus 5.5 --- wai/extensions/sink-energy.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/wai/extensions/sink-energy.md b/wai/extensions/sink-energy.md index 2ad8ffa28..e8e488029 100644 --- a/wai/extensions/sink-energy.md +++ b/wai/extensions/sink-energy.md @@ -294,19 +294,21 @@ read from, with what was measured and against what. sequences. Decoding energy was 17.60% lower, encoding energy 17.46% lower and the bitrate 3.38% lower, each a Bjøntegaard delta at equal matched PSNR (mPSNR, computed against the source), against encoding each sequence at its source's 120 frames per - second. A trained classifier picks, per sequence and per quantisation setting, the - frame rate the encoder codes at, the source's or a lower one; where it is lower, the - decoder decodes fewer frames than the source has. One software encoder and one - software decoder of HEVC were measured, by the processor's own energy counter, idle - subtracted. + second. A trained classifier picks, per sequence and per rate-factor setting (the + table covers four), the frame rate the encoder codes at, the source's or a lower one; + where it is lower, the decoder decodes fewer frames than the source has. One software + encoder and one software decoder of HEVC were measured, by the processor's own energy + counter, idle subtracted. 3. **Lower resolution or coarser quantisation, chosen by the encoder.** arXiv 2511.00707, Table 4, its row ρ = 0.05. Decoding energy was 53.54% lower and encoding energy 51.22% lower, for an average loss of 1.68 VMAF points and 3.67 dB PSNR, against the configuration chosen with no loss allowed (ρ = 0). Per sequence, the encoder takes, of the resolutions and quantisers whose predicted VMAF is within 5% of the best predicted, the one of least predicted energy. One software encoder and one software decoder of VVC - were measured on UHD sources at 60 frames per second, by the processor's own energy - counter read through a software tool. + were measured on UHD sources at 60 frames per second, by a software tool reading the + processor's energy counter and the graphics processor's; the paper does not state + whether the idle energy is subtracted, nor whether Table 4's savings are the measured + or the predicted energy of the configurations chosen. How they bear on this extension: -- 2.54.0 (Apple Git-157) From 6eb0747681a03f8d437d045aa4f304f676898111 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 3 Nov 2026 22:44:27 -0500 Subject: [PATCH 06/29] CI: one pinned wasmtime, run with canonical NaNs and deterministic relaxed SIMD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WAI's WebAssembly legs ran two stock wasmtime versions: 49.0.2 in the quantum and face install steps, and 38.0.3 in wai-ops.yml and wai-vector.yml (wai-ops runs the wasm32-wasip2 decode determinism-tiers §5 cites). A stock wasmtime leaves NaN canonicalisation off and lets each relaxed SIMD instruction take the host's own behaviour, so the bits of a NaN a module reads back, and the result of a relaxed instruction, were the engine's choice. Every install step now pins wasmtime 50.0.1 by version and SHA-256 (the x86_64 Linux release archive, 685466455dd4...98a5, the digest the release lists), and every run passes -W nan-canonicalization=y -W relaxed-simd-deterministic=y wasmtime's two determinism settings: every NaN a float instruction makes is the canonical one, and each relaxed SIMD instruction has one behaviour on every host. The flag names are 50.0.1's own, read from that release's `wasmtime run -W help` on the build host. The two 38.0.3 steps gain the architecture check and the digest the others already had. Both settings took effect on the build host (arm64, the 50.0.1 release for that platform). f32.add of a NaN with payload 0x200001 and 1.0 gives 0x7fe00001 without the flags and the canonical 0x7fc00000 with them. The fixed relaxed behaviour is the engine's, not always the one WebAssembly's deterministic profile prescribes: f32x4.relaxed_madd(1+2^-12, 1+2^-12, -(1+2^-11)) gives the fused 2^-24 with and without the flag, where the profile's choice (parameter 0) is the unfused fadd(fmul), which gives 0. The workflows' comment says so. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-conformance.yml | 32 +++++++++++++++------- .github/workflows/wai-ops.yml | 29 +++++++++++++++----- .github/workflows/wai-quantum-cli.yml | 22 +++++++++------ .github/workflows/wai-quantum-formats.yml | 16 +++++++---- .github/workflows/wai-quantum-lang.yml | 14 +++++++--- .github/workflows/wai-quantum-pulse.yml | 14 +++++++--- .github/workflows/wai-quantum-registry.yml | 14 +++++++--- .github/workflows/wai-quantum-verdict.yml | 16 +++++++---- .github/workflows/wai-vector.yml | 29 +++++++++++++++----- 9 files changed, 132 insertions(+), 54 deletions(-) diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index 7ec3144d5..cd3b57a60 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -969,7 +969,13 @@ jobs: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -979,18 +985,18 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" - name: tests/bit_identity.rs under wasmtime working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: cargo test --locked --target wasm32-wasip2 --features full --test bit_identity # The filters are module paths, so a module that is renamed or loses its tests would @@ -1000,7 +1006,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail mods="quantum ijson receipt_pins quantum_fabric quantum_source quantum_channel quantum_qudit quantum_receipt engine::synth engine::compile engine::estimate" @@ -1083,7 +1089,7 @@ jobs: set -euo pipefail cargo build --locked --target wasm32-wasip2 --no-default-features --features quantum --bin wai_quantum_vectors \ --config 'profile.dev.package.wai.codegen-units=1' - wasmtime run --dir ../quantum-conformance::/corpus \ + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir ../quantum-conformance::/corpus \ target/wasm32-wasip2/debug/wai_quantum_vectors.wasm verify /corpus # The face generator under wasm32-wasip2, run by wasmtime (extensions/avatars.md §7.8): @@ -1113,7 +1119,13 @@ jobs: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -1123,11 +1135,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" @@ -1141,7 +1153,7 @@ jobs: cargo build --locked --release --target wasm32-wasip2 --no-default-features --features avatar_face --bin wai_avatar_face_vectors \ --config 'profile.release.package.wai.codegen-units=1' t="${CARGO_TARGET_DIR:-target}" - wasmtime run --dir ../live-conformance/avatar-face::/corpus \ + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir ../live-conformance/avatar-face::/corpus \ "$t/wasm32-wasip2/release/wai_avatar_face_vectors.wasm" verify /corpus | tee "$RUNNER_TEMP/face-wasm.log" [ "$(grep -c '^PASS ' "$RUNNER_TEMP/face-wasm.log")" = 3 ] || { echo "::error::not every check passed under wasm32"; exit 1; } diff --git a/.github/workflows/wai-ops.yml b/.github/workflows/wai-ops.yml index dad94e41b..996ad1b1a 100644 --- a/.github/workflows/wai-ops.yml +++ b/.github/workflows/wai-ops.yml @@ -207,14 +207,29 @@ jobs: rustup target add wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - name: Install wasmtime 38.0.3 + # The release binary, pinned by version and SHA-256, rather than a build from source. + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail - v=38.0.3 - curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/wasmtime.tar.xz" \ - "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/wasmtime-v$v-x86_64-linux.tar.xz" - tar -C "$RUNNER_TEMP" -xf "$RUNNER_TEMP/wasmtime.tar.xz" + [ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasmtime archive is x86_64; this host is $(uname -m)"; exit 1; } + if ! command -v xz >/dev/null 2>&1; then + SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq xz-utils + fi + v=50.0.1 + f="wasmtime-v$v-x86_64-linux.tar.xz" + curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ + "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - + tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # The tests read committed files through CARGO_MANIFEST_DIR/.., so the wai @@ -225,7 +240,7 @@ jobs: run: | set -euo pipefail W=$(cd .. && pwd) - CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run --dir $W::$W" \ + CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir $W::$W" \ cargo test --locked --release --target wasm32-wasip2 - name: Build wai-verify for wasm32-wasip2 @@ -239,7 +254,7 @@ jobs: run: | set -euo pipefail wasmtime --version - wasmtime run --dir .::/wai wai-verify/target/wasm32-wasip2/release/wai-verify.wasm vectors verify /wai + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir .::/wai wai-verify/target/wasm32-wasip2/release/wai-verify.wasm vectors verify /wai node: needs: relevant diff --git a/.github/workflows/wai-quantum-cli.yml b/.github/workflows/wai-quantum-cli.yml index d990712c0..93916b1a1 100644 --- a/.github/workflows/wai-quantum-cli.yml +++ b/.github/workflows/wai-quantum-cli.yml @@ -111,7 +111,13 @@ jobs: cargo build --locked --release bash smoke.sh ./target/release/wai-quantum # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -121,11 +127,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # The same source as a WASI component: a circuit's state hash, a run's @@ -142,23 +148,23 @@ jobs: printf 'OPENQASM 3.0; qubit[3] q; h q[0]; cx q[0], q[1]; t q[2]; cx q[1], q[2];' > "$RUNNER_TEMP/p.qasm" for args in "lab run ghz -n 5" "lab receipt ghz -n 3 --demo-key"; do $n $args > "$RUNNER_TEMP/native.txt" 2> /dev/null - wasmtime run "$w" $args > "$RUNNER_TEMP/wasi.txt" 2> /dev/null + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y "$w" $args > "$RUNNER_TEMP/wasi.txt" 2> /dev/null cmp "$RUNNER_TEMP/native.txt" "$RUNNER_TEMP/wasi.txt" || { echo "::error::$args differs under wasmtime"; diff "$RUNNER_TEMP/native.txt" "$RUNNER_TEMP/wasi.txt" || true; exit 1; } done $n run --demo-key --json --shots 100 < "$RUNNER_TEMP/p.qasm" 2> /dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); j["result"].pop("elapsed_ms"); j["result"]["receipt"].pop("digest"); j["result"]["receipt"].pop("signature"); print(json.dumps(j, sort_keys=True))' > "$RUNNER_TEMP/native.json" - wasmtime run "$w" run --demo-key --json --shots 100 < "$RUNNER_TEMP/p.qasm" 2> /dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); j["result"].pop("elapsed_ms"); j["result"]["receipt"].pop("digest"); j["result"]["receipt"].pop("signature"); print(json.dumps(j, sort_keys=True))' > "$RUNNER_TEMP/wasi.json" + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y "$w" run --demo-key --json --shots 100 < "$RUNNER_TEMP/p.qasm" 2> /dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); j["result"].pop("elapsed_ms"); j["result"]["receipt"].pop("digest"); j["result"]["receipt"].pop("signature"); print(json.dumps(j, sort_keys=True))' > "$RUNNER_TEMP/wasi.json" cmp "$RUNNER_TEMP/native.json" "$RUNNER_TEMP/wasi.json" || { echo "::error::a run's result differs under wasmtime"; exit 1; } # A compile to Clifford+T (rotation synthesis in exact and fixed-point # arithmetic, the passes, the certificate and its seal): byte for byte. printf 'OPENQASM 3.0; qubit[2] q; h q[0]; rz(0.3) q[0]; cx q[0], q[1]; ry(1.1) q[1]; cp(0.7) q[0], q[1];' > "$RUNNER_TEMP/angles.qasm" $n compile --demo-key --json --level 2 --epsilon 1e-8 < "$RUNNER_TEMP/angles.qasm" > "$RUNNER_TEMP/native-compile.json" 2> /dev/null - wasmtime run "$w" compile --demo-key --json --level 2 --epsilon 1e-8 < "$RUNNER_TEMP/angles.qasm" > "$RUNNER_TEMP/wasi-compile.json" 2> /dev/null + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y "$w" compile --demo-key --json --level 2 --epsilon 1e-8 < "$RUNNER_TEMP/angles.qasm" > "$RUNNER_TEMP/wasi-compile.json" 2> /dev/null cmp "$RUNNER_TEMP/native-compile.json" "$RUNNER_TEMP/wasi-compile.json" || { echo "::error::a compile differs under wasmtime"; exit 1; } # A run on a device target: the layout search, the noise derived and # the density matrix, the same bits. for t in synthetic-line-5 synthetic-all-5; do $n run --demo-key --json --shots 100 --target $t < "$RUNNER_TEMP/p.qasm" 2> /dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); j["result"]["receipt"].pop("digest"); j["result"]["receipt"].pop("signature"); print(json.dumps(j, sort_keys=True))' > "$RUNNER_TEMP/native-$t.json" - wasmtime run "$w" run --demo-key --json --shots 100 --target $t < "$RUNNER_TEMP/p.qasm" 2> /dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); j["result"]["receipt"].pop("digest"); j["result"]["receipt"].pop("signature"); print(json.dumps(j, sort_keys=True))' > "$RUNNER_TEMP/wasi-$t.json" + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y "$w" run --demo-key --json --shots 100 --target $t < "$RUNNER_TEMP/p.qasm" 2> /dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); j["result"]["receipt"].pop("digest"); j["result"]["receipt"].pop("signature"); print(json.dumps(j, sort_keys=True))' > "$RUNNER_TEMP/wasi-$t.json" cmp "$RUNNER_TEMP/native-$t.json" "$RUNNER_TEMP/wasi-$t.json" || { echo "::error::a run on $t differs under wasmtime"; exit 1; } done # A resource estimate (the counts, each rotation's synthesis, the @@ -166,7 +172,7 @@ jobs: # the seal): byte for byte. printf 'OPENQASM 3.0; qubit[4] q; h q; ctrl(3) @ x q[0], q[1], q[2], q[3]; rz(0.3) q[0]; ry(1.1) q[2]; t q[1];' > "$RUNNER_TEMP/est.qasm" $n estimate --demo-key --json --qubit us-e3 < "$RUNNER_TEMP/est.qasm" > "$RUNNER_TEMP/native-estimate.json" 2> /dev/null - wasmtime run "$w" estimate --demo-key --json --qubit us-e3 < "$RUNNER_TEMP/est.qasm" > "$RUNNER_TEMP/wasi-estimate.json" 2> /dev/null + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y "$w" estimate --demo-key --json --qubit us-e3 < "$RUNNER_TEMP/est.qasm" > "$RUNNER_TEMP/wasi-estimate.json" 2> /dev/null cmp "$RUNNER_TEMP/native-estimate.json" "$RUNNER_TEMP/wasi-estimate.json" || { echo "::error::an estimate differs under wasmtime"; exit 1; } echo "the WASI component's output equals the native build's" diff --git a/.github/workflows/wai-quantum-formats.yml b/.github/workflows/wai-quantum-formats.yml index 093378137..0e2f88e3b 100644 --- a/.github/workflows/wai-quantum-formats.yml +++ b/.github/workflows/wai-quantum-formats.yml @@ -129,7 +129,13 @@ jobs: rustup target add --toolchain 1.98.0 wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -139,11 +145,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # A debug build, so integer overflow panics instead of wrapping. The test @@ -153,7 +159,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --features full --test formats_conformance 2>&1 | tee "$RUNNER_TEMP/wasip2.log" @@ -165,7 +171,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS: "true" run: | set -euo pipefail diff --git a/.github/workflows/wai-quantum-lang.yml b/.github/workflows/wai-quantum-lang.yml index a0842fe9c..2cf0b349b 100644 --- a/.github/workflows/wai-quantum-lang.yml +++ b/.github/workflows/wai-quantum-lang.yml @@ -116,7 +116,13 @@ jobs: rustup target add --toolchain 1.98.0 wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -126,11 +132,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # A debug build, so integer overflow panics instead of wrapping. The test @@ -140,7 +146,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --features full --test qasm3_conformance 2>&1 | tee "$RUNNER_TEMP/wasip2.log" diff --git a/.github/workflows/wai-quantum-pulse.yml b/.github/workflows/wai-quantum-pulse.yml index 9ec9024a0..4ef882a75 100644 --- a/.github/workflows/wai-quantum-pulse.yml +++ b/.github/workflows/wai-quantum-pulse.yml @@ -172,7 +172,13 @@ jobs: rustup target add --toolchain 1.98.0 wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -182,11 +188,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # A debug build, so integer overflow panics instead of wrapping. @@ -194,7 +200,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --features full --lib -- qasm::pulse:: quantum_pulse:: engine::pulse:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log" diff --git a/.github/workflows/wai-quantum-registry.yml b/.github/workflows/wai-quantum-registry.yml index 52a7a0471..502f2257e 100644 --- a/.github/workflows/wai-quantum-registry.yml +++ b/.github/workflows/wai-quantum-registry.yml @@ -140,7 +140,13 @@ jobs: rustup target add --toolchain 1.98.0 wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -150,11 +156,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # A debug build, so integer overflow panics instead of wrapping. @@ -162,7 +168,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --features full --lib engine::registry:: 2>&1 | tee "$RUNNER_TEMP/wasip2.log" diff --git a/.github/workflows/wai-quantum-verdict.yml b/.github/workflows/wai-quantum-verdict.yml index 597d631c9..6fc3e62f9 100644 --- a/.github/workflows/wai-quantum-verdict.yml +++ b/.github/workflows/wai-quantum-verdict.yml @@ -135,7 +135,13 @@ jobs: rustup target add --toolchain 1.98.0 wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # The release binary, pinned by version and SHA-256, rather than a build from source. - - name: Install wasmtime 49.0.2 + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail @@ -145,11 +151,11 @@ jobs: $SUDO apt-get update -qq $SUDO apt-get install -y -qq xz-utils fi - v=49.0.2 + v=50.0.1 f="wasmtime-v$v-x86_64-linux.tar.xz" curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" - echo "a4d6e9e3a5a60f527cf7793d674c48930c80c2e8977995b8a275cad3254b9322 $RUNNER_TEMP/$f" | sha256sum -c - + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # A debug build, so integer overflow panics instead of wrapping. The test @@ -159,7 +165,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --features full --test intent_conformance 2>&1 | tee "$RUNNER_TEMP/wasip2.log" @@ -170,7 +176,7 @@ jobs: shell: bash working-directory: wai/wai-quantum env: - CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y run: | set -euo pipefail cargo test --locked --target wasm32-wasip2 --features full --lib -- engine::verdict:: engine::repair:: engine::refusal:: 2>&1 | tee "$RUNNER_TEMP/wasip2-lib.log" diff --git a/.github/workflows/wai-vector.yml b/.github/workflows/wai-vector.yml index 10a2e156d..01757a92a 100644 --- a/.github/workflows/wai-vector.yml +++ b/.github/workflows/wai-vector.yml @@ -178,14 +178,29 @@ jobs: rustup target add wasm32-wasip2 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" - - name: Install wasmtime 38.0.3 + # The release binary, pinned by version and SHA-256, rather than a build from source. + # Every WAI workflow that runs wasmtime pins this one version, and every run passes + # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float + # instruction makes is the canonical one, and each relaxed SIMD instruction has one + # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed + # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, + # where WebAssembly's deterministic profile prescribes the unfused result. + - name: Install wasmtime 50.0.1 shell: bash run: | set -euo pipefail - v=38.0.3 - curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/wasmtime.tar.xz" \ - "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/wasmtime-v$v-x86_64-linux.tar.xz" - tar -C "$RUNNER_TEMP" -xf "$RUNNER_TEMP/wasmtime.tar.xz" + [ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasmtime archive is x86_64; this host is $(uname -m)"; exit 1; } + if ! command -v xz >/dev/null 2>&1; then + SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo" + $SUDO apt-get update -qq + $SUDO apt-get install -y -qq xz-utils + fi + v=50.0.1 + f="wasmtime-v$v-x86_64-linux.tar.xz" + curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ + "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - + tar -xJf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" echo "$RUNNER_TEMP/wasmtime-v$v-x86_64-linux" >> "$GITHUB_PATH" # The tests read committed files through CARGO_MANIFEST_DIR/.., so the wai @@ -196,7 +211,7 @@ jobs: run: | set -euo pipefail W=$(cd .. && pwd) - CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run --dir $W::$W" \ + CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir $W::$W" \ cargo test --locked --release --target wasm32-wasip2 - name: Build the tool for wasm32-wasip2 @@ -210,7 +225,7 @@ jobs: run: | set -euo pipefail wasmtime --version - wasmtime run --dir .::/wai wai-vector/target/wasm32-wasip2/release/wai-vector.wasm vectors verify /wai + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir .::/wai wai-vector/target/wasm32-wasip2/release/wai-vector.wasm vectors verify /wai node: needs: relevant -- 2.54.0 (Apple Git-157) From 290410864df7cdc1d6bac567e21d822176281349 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 3 Nov 2026 22:44:27 -0500 Subject: [PATCH 07/29] wai-web-wasm: the packages validate with relaxed SIMD and threads off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit determinism-tiers §5 says a WebAssembly decoder at decode-equivalence MUST NOT use relaxed SIMD, and that the browser crate was validated without it by hand, "which CI does not check". CI now checks it. wasm-tools 1.250.0, pinned by version and SHA-256, validates the node bundle and the platform_decode bundle the job builds, and the package of record the site serves (reach/record/wai_web_bg.wasm), with --features=-relaxed-simd,-threads. A relaxed SIMD instruction, a shared memory or an atomic in any of them fails the job. Before the packages, the step shows that it can fail. A planted module with i8x16.relaxed_swizzle and one with a shared memory each validate with the default features, and each is refused with them off for its own feature's reason (the error text is matched). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-web-wasm.yml | 48 +++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wai-web-wasm.yml b/.github/workflows/wai-web-wasm.yml index 0d018d5fb..454539028 100644 --- a/.github/workflows/wai-web-wasm.yml +++ b/.github/workflows/wai-web-wasm.yml @@ -31,7 +31,11 @@ name: wai-web wasm32 # Python verifier with the same verdicts; # - a byte comparison of the demo's staged int_synth copies with the corpus; # - the crate's wasm_tests under wasm-bindgen-test-runner, and its native unit -# tests on the host. +# tests on the host; +# - wasm-tools validate on both bundles built here and on the package of record +# (reach/record/wai_web_bg.wasm) with relaxed SIMD and threads turned off: a +# WebAssembly decoder at decode-equivalence uses no relaxed SIMD instruction +# (determinism-tiers §5), and the bundles use no shared memory or atomic. # # The full-scale int_hyper wasm decode needs vectors regenerated from a # pretrained model; wai-sota-vectors.yml runs it after regenerating them. @@ -148,6 +152,48 @@ jobs: target/wasm32-unknown-unknown/release/wai_web.wasm test -s demo/pkg-platform/wai_platform_decode.js grep -q "platformChoose" demo/pkg-platform/wai_platform_decode.js + # The release binary, pinned by version and SHA-256, rather than a build from source. + - name: Install wasm-tools 1.250.0 + shell: bash + run: | + set -euo pipefail + [ "$(uname -m)" = x86_64 ] || { echo "::error::the pinned wasm-tools archive is x86_64; this host is $(uname -m)"; exit 1; } + v=1.250.0 + f="wasm-tools-$v-x86_64-linux.tar.gz" + curl --proto '=https' --tlsv1.2 -sSfL -o "$RUNNER_TEMP/$f" \ + "https://github.com/bytecodealliance/wasm-tools/releases/download/v$v/$f" + echo "b746c34e7c4162b8812eb29397ebe076834e496a8c46fe68d793379a2741eb50 $RUNNER_TEMP/$f" | sha256sum -c - + tar -xzf "$RUNNER_TEMP/$f" -C "$RUNNER_TEMP" + echo "$RUNNER_TEMP/wasm-tools-$v-x86_64-linux" >> "$GITHUB_PATH" + # A WebAssembly decoder at decode-equivalence MUST NOT use a relaxed SIMD + # instruction, whose result the specification lets each engine choose + # (determinism-tiers §5). Both bundles built above and the package of record the + # site serves must validate with relaxed SIMD and threads (shared memory and + # atomics) turned off. First the check is shown able to fail: a planted relaxed + # SIMD module and a planted shared-memory module are each valid with the features + # on, and refused with them off for the feature's own reason. + - name: wai-web's packages validate with relaxed SIMD and threads off + shell: bash + working-directory: wai/wai-web + run: | + set -euo pipefail + F=--features=-relaxed-simd,-threads + wasm-tools --version + printf '(module (func (export "f") (param v128 v128) (result v128) local.get 0 local.get 1 i8x16.relaxed_swizzle))' > "$RUNNER_TEMP/relaxed.wat" + printf '(module (memory 1 1 shared) (func (export "f") (result i32) i32.const 0 i32.atomic.load))' > "$RUNNER_TEMP/shared.wat" + for p in "relaxed:relaxed SIMD support is not enabled" "shared:threads must be enabled"; do + m="$RUNNER_TEMP/${p%%:*}.wat" + wasm-tools validate "$m" + if wasm-tools validate "$F" "$m" 2> "$RUNNER_TEMP/planted.err"; then + echo "::error::the planted module ${p%%:*}.wat validated with $F"; exit 1 + fi + grep -q "${p#*:}" "$RUNNER_TEMP/planted.err" || { echo "::error::${p%%:*}.wat was refused for another reason"; cat "$RUNNER_TEMP/planted.err"; exit 1; } + echo "planted ${p%%:*}.wat: refused ($(grep -o "${p#*:}" "$RUNNER_TEMP/planted.err"))" + done + for m in demo/pkg-node/wai_web_bg.wasm demo/pkg-platform/wai_platform_decode_bg.wasm reach/record/wai_web_bg.wasm; do + wasm-tools validate "$F" "$m" || { echo "::error::$m does not validate with relaxed SIMD and threads off"; exit 1; } + echo "$m (SHA-256 $(sha256sum "$m" | cut -d' ' -f1)): valid with relaxed SIMD and threads off" + done # The JS surface itself (pick, companionsOf, verifyPin, rendition() and # companion() refusing what is never presented or does not match its digest, # the held-sets conversion, and the WAI2 parse at 32-bit usize) runs only on -- 2.54.0 (Apple Git-157) From b57022ecfea66248c97c8e72fb3644b7d67520de Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Tue, 3 Nov 2026 22:44:27 -0500 Subject: [PATCH 08/29] wai-byte-equality: a full runner disk fails as infrastructure, before the checkout Main's run 17676 at 2bef11f71 failed on both arm64 legs with "No space left on device": linux-aarch64 in the checkout's `git fetch`, and macOS in the toolchain's temporary files. Both runners live on one host, whose disk was full. No fixture was decoded, yet the cross-architecture matrix read as red. Each leg now begins, before the checkout, with a step named "infrastructure: this runner's disk has room for the leg". It measures the free space under the workspace, the runner's temporary directory, TMPDIR and HOME. Below a floor of 4 GiB it fails with an error that begins "infrastructure:" and says that no fixture was decoded. The leg itself writes under 1 GiB (measured on the build host: about 270 MiB of working tree and a 67 MiB release target), and a cold toolchain install about 1 GiB more. A disk can also fill while the leg runs, from the jobs that share it. A second step runs only when a step after the check failed. It measures again, and names the failure infrastructure when the disk is below the floor, or says that the failure is not the disk. Both steps are bash and skip on Windows, as the matrix comment says. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/wai-byte-equality.yml | 55 +++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/.github/workflows/wai-byte-equality.yml b/.github/workflows/wai-byte-equality.yml index 16f3c1580..deab15f3f 100644 --- a/.github/workflows/wai-byte-equality.yml +++ b/.github/workflows/wai-byte-equality.yml @@ -164,11 +164,44 @@ jobs: # Windows x86_64 leg removed 2026-09-30: runner offline. Restore by # re-adding `- { os: windows, label: windows-x86_64 }` here once the # runner is back online. + # The two disk steps below are bash, and skip on Windows. include: - { os: linux, label: linux-x86_64 } - { os: linux-arm64, label: linux-aarch64 } - { os: macos, label: macos-arm64 (reference) } + env: + # The floor of the disk check below, in GiB. The leg writes under 1 GiB (a + # shallow checkout of about 0.3 GiB plus its pack, and a release target under + # 0.1 GiB, measured 2026-11-03), and a cold toolchain install about 1 GiB more + # under HOME; the rest is room for the jobs that share the disk. + DISK_FLOOR_GIB: '4' steps: + # Infrastructure first, before the checkout writes anything. Main's run 17676 + # failed on both arm64 legs with "No space left on device", linux-aarch64 in + # `git fetch` and macOS in the toolchain's temporary files: the hosts' disks were + # full and no fixture was decoded, yet the matrix read as a byte-equality failure. + # This step measures the free space under every directory the leg writes to (the + # workspace, the runner's temporary directory, TMPDIR, and HOME, where the + # toolchain lives). Below the floor it fails, and its name and its error say + # infrastructure, so a full disk is not read as a divergence. + - name: "infrastructure: this runner's disk has room for the leg" + id: disk + if: matrix.os != 'windows' + shell: bash + run: | + set -euo pipefail + short=0 + for d in "${GITHUB_WORKSPACE:-$PWD}" "${RUNNER_TEMP:-/tmp}" "${TMPDIR:-/tmp}" "$HOME"; do + p=$d; while [ ! -e "$p" ]; do p=$(dirname "$p"); done + set -- $(df -Pk "$p" | awk 'NR == 2 {print $4, $6}') + echo "$d: $(($1 / 1048576)) GiB free on $2" + if [ "$1" -lt $((DISK_FLOOR_GIB * 1048576)) ]; then + echo "::error::infrastructure: ${{ matrix.label }}: $d is on $2, which has $(($1 / 1024)) MiB free, below the floor of $DISK_FLOOR_GIB GiB. The runner host's disk is full: no fixture was decoded, so this is not a byte-equality failure." + short=1 + fi + done + exit $short + - name: Checkout uses: actions/checkout@v4 @@ -195,3 +228,25 @@ jobs: - name: Decode fixtures, assert golden bytes working-directory: wai/byte-exact-conformance run: cargo test --release -- --nocapture + + # A disk can also fill while the leg runs, from the jobs that share it (three + # conformance jobs at once filled the arm64 VM's data disk mid-link). When a step + # after the check failed, the same measurement is taken again: below the floor, + # the failure is named infrastructure; with room, it stands as it is. + - name: "infrastructure: was the disk full when the leg failed?" + if: failure() && steps.disk.outcome == 'success' + shell: bash + run: | + set -euo pipefail + short=0 + for d in "${GITHUB_WORKSPACE:-$PWD}" "${RUNNER_TEMP:-/tmp}" "${TMPDIR:-/tmp}" "$HOME"; do + p=$d; while [ ! -e "$p" ]; do p=$(dirname "$p"); done + set -- $(df -Pk "$p" | awk 'NR == 2 {print $4, $6}') + echo "$d: $(($1 / 1048576)) GiB free on $2" + if [ "$1" -lt $((DISK_FLOOR_GIB * 1048576)) ]; then + echo "::error::infrastructure: ${{ matrix.label }}: $d is on $2, which has $(($1 / 1024)) MiB free, below the floor of $DISK_FLOOR_GIB GiB. The disk filled while the leg ran: read the failure above as the runner host's disk, not as a byte-equality failure." + short=1 + fi + done + [ "$short" = 1 ] || echo "Every disk this leg writes to has room: the failure above is not the disk." + exit $short -- 2.54.0 (Apple Git-157) From 888cc3ba20de8a94a7a9d442922d89e330443495 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 4 Nov 2026 02:51:25 -0500 Subject: [PATCH 09/29] CI: a check holds every workflow to the one pinned wasmtime and both its settings The install steps' comment said every WAI workflow that runs wasmtime pins one version and passes both determinism settings, but nothing enforced it: a step that ran a stock or differently pinned wasmtime would have merged green. scripts/check-wasmtime-pin.py now fails, in the repository checks, any workflow that 1. downloads a wasmtime release other than 50.0.1, or checks it against any SHA-256 but that archive's; 2. installs wasmtime another way (cargo install, the install script, an action); 3. runs wasmtime (run, serve, compile, or a module given to it) or names it as a CARGO_TARGET_*_RUNNER without -W nan-canonicalization=y and -W relaxed-simd-deterministic=y. It also fails when it finds no download or no run, so a check that looked in the wrong place cannot pass. On this tree it finds the downloads and runs it should. Its self-test (scripts/check-wasmtime-pin-selftest.sh) passes a scratch workflow shaped like these and fails each way of breaking it that the self-test lists, matching the reason in the output. The comment's relaxed_madd claim was measured on arm64 only. It now rests on both architectures, measured on an arm64 macOS host and an x86_64 Linux host with a small probe (wasmtime 50.0.1's release for the host, checked against the digest the release lists): host Darwin arm64; wasmtime 50.0.1 (c307ec81d 2026-10-29) f32.add(bits 0x7fa00001, 1.0) [no flags]: 0x7fe00001 f32x4.relaxed_madd(1+2^-12, 1+2^-12, -(1+2^-11)) [no flags]: 0x33800000 f32.add(bits 0x7fa00001, 1.0) [both flags]: 0x7fc00000 f32x4.relaxed_madd(1+2^-12, 1+2^-12, -(1+2^-11)) [both flags]: 0x33800000 an unknown -W option is refused: error: failed to parse -W / --wasm option: unknown option: no-such-option host Linux x86_64; wasmtime 50.0.1 (c307ec81d 2026-10-29) (the same five lines, bit for bit) 0x33800000 is 2^-24, the fused result; the unfused fadd(fmul) gives 0. So the NaN setting takes effect on both, and relaxed_madd is fused on both with and without the relaxed setting. That matches 50.0.1's source (the wasmtime-internal-cranelift crate, read from the registry): the translator lowers F32x4/F64x2RelaxedMadd through fma_f32x4/fma_f64x2 (src/translate/code_translator.rs L2530-2539), which emit a native fma on a host that has one, a fused library call under relaxed-simd-deterministic on a host that has none, and the unfused fmul and fadd only on a host with no FMA and the setting off (src/func_environ.rs L5907-5925). The setting is what makes the result fused on every host. And since an unknown -W option stops wasmtime, a run that exits 0 with the two flags has parsed them. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/repo-checks.yml | 8 ++ .github/workflows/wai-conformance.yml | 12 +- .github/workflows/wai-ops.yml | 6 +- .github/workflows/wai-quantum-cli.yml | 6 +- .github/workflows/wai-quantum-formats.yml | 6 +- .github/workflows/wai-quantum-lang.yml | 6 +- .github/workflows/wai-quantum-pulse.yml | 6 +- .github/workflows/wai-quantum-registry.yml | 6 +- .github/workflows/wai-quantum-verdict.yml | 6 +- .github/workflows/wai-vector.yml | 6 +- scripts/check-wasmtime-pin-selftest.sh | 107 +++++++++++++++ scripts/check-wasmtime-pin.py | 144 +++++++++++++++++++++ 12 files changed, 299 insertions(+), 20 deletions(-) create mode 100755 scripts/check-wasmtime-pin-selftest.sh create mode 100755 scripts/check-wasmtime-pin.py diff --git a/.github/workflows/repo-checks.yml b/.github/workflows/repo-checks.yml index 3fd6a9476..81791ead5 100644 --- a/.github/workflows/repo-checks.yml +++ b/.github/workflows/repo-checks.yml @@ -87,6 +87,14 @@ jobs: if: ${{ !cancelled() }} run: ./scripts/pr-touches-paths-selftest.sh + # ── One pinned wasmtime, run with both determinism settings ───────────── + - name: "workflow hygiene: the wasmtime pin check fails closed (self-test)" + if: ${{ !cancelled() }} + run: bash scripts/check-wasmtime-pin-selftest.sh + - name: "workflow hygiene: every wasmtime is the pinned release, run with both determinism settings" + if: ${{ !cancelled() }} + run: python3 -I scripts/check-wasmtime-pin.py + # ── Code cites no section its SPEC lacks; WAI's cited sections exist ──── - name: "spec references: the check can fail (self-test)" if: ${{ !cancelled() }} diff --git a/.github/workflows/wai-conformance.yml b/.github/workflows/wai-conformance.yml index cd3b57a60..db7fe7c5e 100644 --- a/.github/workflows/wai-conformance.yml +++ b/.github/workflows/wai-conformance.yml @@ -973,8 +973,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | @@ -1123,8 +1125,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-ops.yml b/.github/workflows/wai-ops.yml index 996ad1b1a..00273b74c 100644 --- a/.github/workflows/wai-ops.yml +++ b/.github/workflows/wai-ops.yml @@ -212,8 +212,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-quantum-cli.yml b/.github/workflows/wai-quantum-cli.yml index 93916b1a1..cf3d37363 100644 --- a/.github/workflows/wai-quantum-cli.yml +++ b/.github/workflows/wai-quantum-cli.yml @@ -115,8 +115,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-quantum-formats.yml b/.github/workflows/wai-quantum-formats.yml index 0e2f88e3b..459cfe70e 100644 --- a/.github/workflows/wai-quantum-formats.yml +++ b/.github/workflows/wai-quantum-formats.yml @@ -133,8 +133,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-quantum-lang.yml b/.github/workflows/wai-quantum-lang.yml index 2cf0b349b..752323b87 100644 --- a/.github/workflows/wai-quantum-lang.yml +++ b/.github/workflows/wai-quantum-lang.yml @@ -120,8 +120,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-quantum-pulse.yml b/.github/workflows/wai-quantum-pulse.yml index 4ef882a75..d8d878a81 100644 --- a/.github/workflows/wai-quantum-pulse.yml +++ b/.github/workflows/wai-quantum-pulse.yml @@ -176,8 +176,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-quantum-registry.yml b/.github/workflows/wai-quantum-registry.yml index 502f2257e..e6be4c3fc 100644 --- a/.github/workflows/wai-quantum-registry.yml +++ b/.github/workflows/wai-quantum-registry.yml @@ -144,8 +144,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-quantum-verdict.yml b/.github/workflows/wai-quantum-verdict.yml index 6fc3e62f9..033e5e093 100644 --- a/.github/workflows/wai-quantum-verdict.yml +++ b/.github/workflows/wai-quantum-verdict.yml @@ -139,8 +139,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/.github/workflows/wai-vector.yml b/.github/workflows/wai-vector.yml index 01757a92a..21da478c6 100644 --- a/.github/workflows/wai-vector.yml +++ b/.github/workflows/wai-vector.yml @@ -183,8 +183,10 @@ jobs: # -W nan-canonicalization=y -W relaxed-simd-deterministic=y: every NaN a float # instruction makes is the canonical one, and each relaxed SIMD instruction has one # behaviour on every host. A stock wasmtime leaves both off. The fixed relaxed - # behaviour is the engine's own: on 50.0.1 (measured on arm64) relaxed_madd is fused, - # where WebAssembly's deterministic profile prescribes the unfused result. + # behaviour is the engine's own: on 50.0.1 relaxed_madd is fused (measured on arm64 and + # x86_64), where WebAssembly's deterministic profile prescribes the unfused result. + # The repository checks (scripts/check-wasmtime-pin.py) fail a workflow that pins another + # release or runs wasmtime without both settings. - name: Install wasmtime 50.0.1 shell: bash run: | diff --git a/scripts/check-wasmtime-pin-selftest.sh b/scripts/check-wasmtime-pin-selftest.sh new file mode 100755 index 000000000..4c68863d7 --- /dev/null +++ b/scripts/check-wasmtime-pin-selftest.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# +# check-wasmtime-pin-selftest.sh — check-wasmtime-pin.py can fail. +# +# A check that cannot fail is decoration. This builds a scratch tree with one workflow +# that installs the pinned wasmtime and runs it the ways the repository's workflows do, +# asserts that the check passes it, and then breaks it each way the property can break +# and asserts that the check fails, saying why. +set -uo pipefail + +here=$(cd "$(dirname "$0")" && pwd) +check="$here/check-wasmtime-pin.py" +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT +W="$tmp/.github/workflows/w.yml" + +mk() { + rm -rf "$tmp/.github" + mkdir -p "$tmp/.github/workflows" + cat > "$W" <<'YML' +jobs: + t: + steps: + # Every run passes -W nan-canonicalization=y -W relaxed-simd-deterministic=y. + - name: Install wasmtime 50.0.1 + run: | + v=50.0.1 + f="wasmtime-v$v-x86_64-linux.tar.xz" + curl -sSfL -o "$RUNNER_TEMP/$f" \ + "https://github.com/bytecodealliance/wasmtime/releases/download/v$v/$f" + echo "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5 $RUNNER_TEMP/$f" | sha256sum -c - + - name: tests under wasmtime + env: + CARGO_TARGET_WASM32_WASIP2_RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y + run: cargo test --target wasm32-wasip2 + - name: the corpus under wasmtime + run: | + W=$(pwd) + CARGO_TARGET_WASM32_WASIP2_RUNNER="wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir $W::$W" \ + cargo test --release --target wasm32-wasip2 + wasmtime --version + wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir .::/w \ + x.wasm verify /w + wasmtime run \ + -W nan-canonicalization=y -W relaxed-simd-deterministic=y x.wasm + cmp a b || { echo "::error::the output differs under wasmtime"; exit 1; } +YML +} + +rep() { # rep : replace the first occurrence in the scratch workflow + python3 -I -c 'import sys; p, a, b = sys.argv[1:]; s = open(p).read(); assert a in s, a; open(p, "w").write(s.replace(a, b, 1))' "$W" "$1" "$2" +} + +fails=0 +expect() { # expect [text the output must hold] + local want=$1 what=$2 needle=${3:-} out rc + out=$(python3 -I "$check" --root "$tmp" 2>&1); rc=$? + if [ "$want" = pass ] && [ $rc -ne 0 ]; then echo "FAIL $what: the check failed"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi + if [ "$want" = fail ] && [ $rc -eq 0 ]; then echo "FAIL $what: the check passed"; fails=$((fails+1)); return; fi + if [ -n "$needle" ] && ! grep -qF -- "$needle" <<<"$out"; then echo "FAIL $what: the output does not say '$needle'"; printf '%s\n' "$out" | sed 's/^/ /'; fails=$((fails+1)); return; fi + echo "ok $what" +} + +mk; expect pass "the pinned release, run every way with both settings, passes" + +mk; rep "v=50.0.1" "v=49.0.2" +expect fail "another version" "downloads wasmtime 49.0.2" + +mk; rep '/download/v$v/$f"' '/download/v38.0.3/wasmtime-v38.0.3-x86_64-linux.tar.xz"'; rep "v=50.0.1" ":" +expect fail "another version, named in the URL" "downloads wasmtime 38.0.3" + +mk; rep "685466455dd4" "685466455dd5" +expect fail "another digest" "not its pinned digest" + +mk; rep '| sha256sum -c -' '> /dev/null' +expect fail "no digest checked" "checks no SHA-256" + +mk; rep "x86_64-linux.tar.xz" "aarch64-linux.tar.xz" +expect fail "an archive with no pinned digest" "has no pinned digest here" + +mk; rep " steps:" " steps: + - run: cargo install wasmtime-cli --locked" +expect fail "wasmtime built from source" "installs wasmtime some other way" + +mk; rep "RUNNER: wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y" "RUNNER: wasmtime" +expect fail "a cargo runner with neither setting" "runs wasmtime without -W nan-canonicalization=y and -W relaxed-simd-deterministic=y" + +mk; rep '--dir $W::$W"' '--dir $W::$W -W nan-canonicalization=y"'; rep 'RUNNER="wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' 'RUNNER="wasmtime run' +expect fail "a runner with one setting" "runs wasmtime without -W relaxed-simd-deterministic=y" + +mk; rep "-W relaxed-simd-deterministic=y --dir .::/w" "--dir .::/w" +expect fail "a run without deterministic relaxed SIMD" "runs wasmtime without -W relaxed-simd-deterministic=y" + +mk; rep " -W nan-canonicalization=y -W relaxed-simd-deterministic=y x.wasm" " x.wasm" +expect fail "a run continued over two lines, with neither setting" "runs wasmtime without" + +mk; rep "wasmtime --version" "wasmtime x.wasm" +expect fail "a module given to wasmtime with no subcommand" "runs wasmtime without" + +mk; rep "wasmtime --version" "wasmtime serve -W nan-canonicalization=y app.wasm" +expect fail "a serve without deterministic relaxed SIMD" "runs wasmtime without -W relaxed-simd-deterministic=y" + +mk; printf 'jobs:\n t:\n steps:\n - run: true\n' > "$W" +expect fail "no workflow that runs wasmtime" "looked in the wrong place" + +if [ $fails -ne 0 ]; then echo "check-wasmtime-pin-selftest: $fails failure(s)"; exit 1; fi +echo "check-wasmtime-pin-selftest: the check passes the pinned, flagged runs and fails each other." diff --git a/scripts/check-wasmtime-pin.py b/scripts/check-wasmtime-pin.py new file mode 100755 index 000000000..1550e0da2 --- /dev/null +++ b/scripts/check-wasmtime-pin.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python3 +"""check-wasmtime-pin.py: every workflow that runs wasmtime runs the one pinned release, +with both of its determinism settings. + +WAI's WebAssembly legs hold a module's output to pinned bits. A stock wasmtime leaves NaN +canonicalisation off and lets each relaxed SIMD instruction take the host's behaviour, and +two versions of it can lower one instruction two ways, so a leg's bits would be the +engine's choice. The install steps' comment says every workflow pins one version and +passes both settings; this makes that true of every workflow, not only of the ones that +comment sits in. + +In every `.github/workflows/*.yml`, with continued lines (`\\` at the end) joined, this +requires that + + 1. every download of a wasmtime release names version PIN (the `v=` the step sets, or + the version in the URL), and the SHA-256 it checks is the digest of that archive in + DIGESTS; + 2. wasmtime is installed no other way (`cargo install wasmtime-cli`, the install + script, or an action); + 3. every `wasmtime run`, `wasmtime serve` or `wasmtime compile`, every `wasmtime` given + a module path, and every `CARGO_TARGET__RUNNER` that is wasmtime, passes + `-W nan-canonicalization=y` and `-W relaxed-simd-deterministic=y`; + 4. at least one download and one run were found, so a check that looked in the wrong + place fails rather than passing on nothing. + + check-wasmtime-pin.py [--root DIR] + +Exit 0 when all four hold, 1 with one line a finding otherwise. Standard library only. +""" +import os +import re +import sys + +PIN = "50.0.1" +# The release archives a workflow may download, by name, with the SHA-256 the release lists. +DIGESTS = { + "wasmtime-v50.0.1-x86_64-linux.tar.xz": "685466455dd42a14774a86c36c8f2e45cbb9bd6d92a850139315df60a75498a5", +} +FLAGS = ("nan-canonicalization=y", "relaxed-simd-deterministic=y") + +DOWNLOAD = re.compile(r"bytecodealliance/wasmtime/releases/download/") +URL_VERSION = re.compile(r"releases/download/v(\d[^/\"'\s]*)/") +SET_VERSION = re.compile(r"^\s*v=[\"']?([^\"'\s;]+)") +ARCHIVE = re.compile(r"wasmtime-v(\$v|\$\{v\}|\d[^-\"'\s]*)-([a-z0-9_]+-[a-z]+)\.tar\.xz") +DIGEST = re.compile(r"\b([0-9a-f]{64})\b.*sha256sum -c") +OTHER_INSTALL = re.compile( + r"cargo\s+(?:\S+\s+)*install\s+(?:\S+\s+)*wasmtime-cli|wasmtime\.dev/install|uses:\s*\S*wasmtime" +) +# wasmtime as a command: run, serve or compile, or given a module path (`wasmtime x.wasm`). +INVOKE = re.compile( + r"(?:^|[\s;&|(`/\"'=])wasmtime\s+(?:(?:run|serve|compile)\b|[^\s-]\S*\.(?:c?wasm|wat)\b)" +) +RUNNER = re.compile(r"CARGO_TARGET_\w+_RUNNER\s*[:=]\s*[\"']?wasmtime\b") + + +def logical_lines(text): + """(first line number, text) with backslash-continued lines joined.""" + out, buf, start = [], "", 0 + for n, line in enumerate(text.splitlines(), 1): + if not buf: + start = n + if line.rstrip().endswith("\\"): + buf += line.rstrip()[:-1] + " " + continue + out.append((start, buf + line)) + buf = "" + if buf: + out.append((start, buf)) + return out + + +def check_file(path, rel, findings): + lines = logical_lines(open(path, encoding="utf-8").read()) + downloads = runs = 0 + for i, (n, line) in enumerate(lines): + code = line.lstrip() + if code.startswith("#"): + continue + if OTHER_INSTALL.search(line): + findings.append(f"{rel}:{n}: installs wasmtime some other way; download the pinned release ({PIN}) by version and SHA-256") + if DOWNLOAD.search(line): + downloads += 1 + m = URL_VERSION.search(line) + ver = m.group(1) if m else None + if ver is None: # the URL takes the version from the step's `v=` + for _, prev in reversed(lines[max(0, i - 12) : i]): + s = SET_VERSION.match(prev) + if s: + ver = s.group(1) + break + if ver != PIN: + findings.append(f"{rel}:{n}: downloads wasmtime {ver or '(version not found)'}; every workflow pins {PIN}") + near = lines[max(0, i - 12) : i + 5] + arch = None + for _, other in near: + a = ARCHIVE.search(other) + if a: + arch = a.group(2) + break + name = f"wasmtime-v{ver}-{arch}.tar.xz" if arch else None + digests = [d.group(1) for _, other in lines[i : i + 5] for d in [DIGEST.search(other)] if d] + if name not in DIGESTS: + findings.append(f"{rel}:{n}: downloads {name or 'an archive whose name was not found'}, which has no pinned digest here") + elif not digests: + findings.append(f"{rel}:{n}: downloads {name} and checks no SHA-256 within four lines") + elif digests[0] != DIGESTS[name]: + findings.append(f"{rel}:{n}: checks {name} against {digests[0][:12]}..., not its pinned digest {DIGESTS[name][:12]}...") + for rx in (INVOKE, RUNNER): + m = rx.search(line) + if m: + runs += 1 + rest = line[m.start() :] + missing = [f for f in FLAGS if f not in rest] + if missing: + findings.append(f"{rel}:{n}: runs wasmtime without {' and '.join('-W ' + f for f in missing)}") + break + return downloads, runs + + +def main(argv): + root = "." + if len(argv) == 3 and argv[1] == "--root": + root = argv[2] + elif len(argv) != 1: + sys.exit("usage: check-wasmtime-pin.py [--root DIR]") + wf = os.path.join(root, ".github", "workflows") + findings, downloads, runs = [], 0, 0 + for name in sorted(os.listdir(wf)) if os.path.isdir(wf) else []: + if name.endswith((".yml", ".yaml")): + d, r = check_file(os.path.join(wf, name), f".github/workflows/{name}", findings) + downloads += d + runs += r + if downloads == 0 or runs == 0: + findings.append(f"found {downloads} wasmtime download(s) and {runs} run(s) under {wf}: this check looked in the wrong place") + for f in findings: + print(f"::error::{f}") + if findings: + return 1 + print(f"check-wasmtime-pin: {downloads} downloads of wasmtime {PIN}, each checked against its digest; {runs} runs, each with both settings") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) -- 2.54.0 (Apple Git-157) From 8a3141366d01c76c25951869257d65313b053b61 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 4 Nov 2026 02:54:27 -0500 Subject: [PATCH 10/29] =?UTF-8?q?determinism-tiers=20=C2=A75:=20CI=20valid?= =?UTF-8?q?ates=20the=20browser=20packages;=20the=20READMEs=20run=20CI's?= =?UTF-8?q?=20wasmtime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit §5 said the browser crate's SIMD validation was done by hand, "which CI does not check". Since "wai-web-wasm: the packages validate with relaxed SIMD and threads off", wai-web-wasm.yml validates both bundles and reach/record/wai_web_bg.wasm with relaxed SIMD and threads disabled. §5 now says so, and says what CI still does not check: that they hold no other SIMD instruction. The changelog gains the CI entry for this branch. The commands that reproduce CI's wasmtime legs ran a stock wasmtime: the WAI Quantum READMEs (English and Chinese), the bit_identity test's header, the WAI README's edge example, and the conformance READMEs (quantum, qasm3, formats, answers, intents). Each now passes -W nan-canonicalization=y -W relaxed-simd-deterministic=y, as CI does. The determinism-cost run scripts under wai/research keep their commands: they record runs already made. The quantum docs check could not see that drift. For a `norun=ci:` block it stripped every leading VAR=value before matching the command against the workflow, and its pattern, \S*, did not read a quoted value. It now reads a quoted or bare value, and when the workflow sets VAR (as an env: key or in a script) the documented value must be one the workflow gives it, so CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime is refused against a workflow that sets it to the flagged runner. Co-Authored-By: Claude Opus 5.5 --- wai/CHANGELOG.md | 14 +++++++++++ wai/README.md | 4 ++-- wai/extensions/determinism-tiers.md | 5 +++- wai/quantum-conformance/README.md | 2 +- wai/quantum-conformance/answers/README.md | 2 +- wai/quantum-conformance/formats/README.md | 4 ++-- wai/quantum-conformance/intents/README.md | 2 +- wai/quantum-conformance/qasm3/README.md | 2 +- wai/tools/quantum-docs/check_docs.py | 29 +++++++++++++++++++---- wai/wai-quantum/README.md | 2 +- wai/wai-quantum/README.zh-CN.md | 2 +- wai/wai-quantum/tests/bit_identity.rs | 2 +- 12 files changed, 54 insertions(+), 16 deletions(-) diff --git a/wai/CHANGELOG.md b/wai/CHANGELOG.md index a57e82118..aaf219337 100644 --- a/wai/CHANGELOG.md +++ b/wai/CHANGELOG.md @@ -6,6 +6,20 @@ wire format may change and such changes are recorded here. ## Unreleased +### CI: WebAssembly runs under one pinned wasmtime with both its determinism settings, and the browser packages hold no relaxed SIMD + +- **CI:** every workflow that runs a module under wasmtime runs wasmtime 50.0.1, pinned by version and SHA-256, with + `-W nan-canonicalization=y -W relaxed-simd-deterministic=y`: every NaN a float instruction makes is the canonical + one, and each relaxed SIMD instruction has one behaviour on every host. `wai-ops.yml` and `wai-vector.yml` ran + 38.0.3, and the quantum and face jobs 49.0.2, each with neither setting. The repository checks + (`scripts/check-wasmtime-pin.py`) fail a workflow that pins another release or runs wasmtime without both. +- **CI:** `wai-web-wasm.yml` validates both of `wai-web`'s bundles and `reach/record/wai_web_bg.wasm` with relaxed SIMD + and threads disabled (`wasm-tools` 1.250.0, pinned the same way), so a relaxed SIMD instruction, a shared memory or + an atomic in any of them fails the job ([determinism-tiers](extensions/determinism-tiers.md) §5). +- The READMEs' commands that reproduce these jobs pass the same two settings. The quantum docs check holds a + `VAR=value` before a command a `ci:` block names to a value the workflow gives `VAR`, so a documented runner cannot + drift from CI's. + ### CI: the maintainers' product names are checked on every pull request - **CI:** the public-name check applies the maintainers' product names, held in the script, on every change, a pull diff --git a/wai/README.md b/wai/README.md index 51364b22f..15f655a79 100644 --- a/wai/README.md +++ b/wai/README.md @@ -329,8 +329,8 @@ earlier releases gave. ```bash cd wai-quantum-cli && cargo build --release --target wasm32-wasip2 --no-default-features -wasmtime run target/wasm32-wasip2/release/wai-quantum.wasm lab run ghz -n 5 -wasmtime run target/wasm32-wasip2/release/wai-quantum.wasm lab receipt ghz -n 3 --demo-key +wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y target/wasm32-wasip2/release/wai-quantum.wasm lab run ghz -n 5 +wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y target/wasm32-wasip2/release/wai-quantum.wasm lab receipt ghz -n 3 --demo-key ``` Both print the identical `state-hash`. A receipt sealed in cloud WASM verifies diff --git a/wai/extensions/determinism-tiers.md b/wai/extensions/determinism-tiers.md index bad01d753..778f75701 100644 --- a/wai/extensions/determinism-tiers.md +++ b/wai/extensions/determinism-tiers.md @@ -533,7 +533,10 @@ shows, by fabric: `wasm32-wasip2` under wasmtime (§6). The browser crate's release build contains no SIMD instruction: on the source of the change that added this subsection it validated with WebAssembly's SIMD and relaxed-SIMD features disabled - (`wasm-tools validate`), which CI does not check. A WebAssembly decoder at this + (`wasm-tools validate`). CI (`wai-web-wasm.yml`) validates both of the crate's + bundles and the package of record, `reach/record/wai_web_bg.wasm`, with relaxed SIMD + and threads disabled, and fails on a module that uses either. CI does not check that + they contain no other SIMD instruction. A WebAssembly decoder at this tier MUST NOT use relaxed-SIMD instructions, whose results the WebAssembly specification lets engines choose among, and MUST NOT depend on the bits of a NaN. - **GPU.** A decoder's `wai-ops/1-i32` graphs ([integer-operators](integer-operators.md) diff --git a/wai/quantum-conformance/README.md b/wai/quantum-conformance/README.md index f87283d9e..a05beafd9 100644 --- a/wai/quantum-conformance/README.md +++ b/wai/quantum-conformance/README.md @@ -43,7 +43,7 @@ Same binary, same source, different architecture: ```sh cargo build --release --target wasm32-wasip2 --no-default-features --features quantum \ --bin wai_quantum_vectors -wasmtime run --dir wai/quantum-conformance::/corpus \ +wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y --dir wai/quantum-conformance::/corpus \ target/wasm32-wasip2/release/wai_quantum_vectors.wasm verify /corpus ``` diff --git a/wai/quantum-conformance/answers/README.md b/wai/quantum-conformance/answers/README.md index 442a55bda..b99f0f7df 100644 --- a/wai/quantum-conformance/answers/README.md +++ b/wai/quantum-conformance/answers/README.md @@ -37,7 +37,7 @@ WAI_ANSWER_BUNDLE_WRITE=1 cargo test --release --features full --test answer_con ## WASI (the edge) ```sh -CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime \ +CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' \ cargo test --target wasm32-wasip2 --features full --test answer_conformance ``` diff --git a/wai/quantum-conformance/formats/README.md b/wai/quantum-conformance/formats/README.md index 1329bfb35..2f215ae79 100644 --- a/wai/quantum-conformance/formats/README.md +++ b/wai/quantum-conformance/formats/README.md @@ -47,7 +47,7 @@ cargo test --release --features full --test formats_mutation -- --nocapture ## WASI (the edge) ```sh -CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime \ +CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' \ cargo test --target wasm32-wasip2 --features full --test formats_conformance ``` @@ -55,7 +55,7 @@ The cases come from the bundle compiled into the test. The hostile-input test runs there too, as a release build with overflow checks on: ```sh -CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS=true CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime \ +CARGO_PROFILE_RELEASE_OVERFLOW_CHECKS=true CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' \ cargo test --release --target wasm32-wasip2 --features full --test formats_mutation ``` diff --git a/wai/quantum-conformance/intents/README.md b/wai/quantum-conformance/intents/README.md index da6291705..a45a0b0fe 100644 --- a/wai/quantum-conformance/intents/README.md +++ b/wai/quantum-conformance/intents/README.md @@ -39,7 +39,7 @@ WAI_INTENT_BUNDLE_WRITE=1 cargo test --release --features full --test intent_con ## WASI (the edge) ```sh -CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime \ +CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' \ cargo test --target wasm32-wasip2 --features full --test intent_conformance ``` diff --git a/wai/quantum-conformance/qasm3/README.md b/wai/quantum-conformance/qasm3/README.md index fd564b25a..dd5647379 100644 --- a/wai/quantum-conformance/qasm3/README.md +++ b/wai/quantum-conformance/qasm3/README.md @@ -35,7 +35,7 @@ WAI_QASM3_BUNDLE_WRITE=1 cargo test --release --features full --test qasm3_confo ## WASI (the edge) ```sh -CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime \ +CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' \ cargo test --target wasm32-wasip2 --features full --test qasm3_conformance ``` diff --git a/wai/tools/quantum-docs/check_docs.py b/wai/tools/quantum-docs/check_docs.py index e52aec074..b1f1efc0f 100644 --- a/wai/tools/quantum-docs/check_docs.py +++ b/wai/tools/quantum-docs/check_docs.py @@ -35,8 +35,9 @@ fenced block is one of these, by its info string (the words after the fence): - ```` ```json ````: parses (`fragment` excepts one). - ```` ``` norun=REASON ````: a block that cannot run here, for a stated reason: `ci:WORKFLOW` (the steps a CI workflow runs: each script it names - exists, and the workflow names it), `install` (each package it installs is - this repository's, at its version), `data` (it needs files the repository + exists, and the workflow names it; a leading `VAR=value` must be a value the + workflow gives VAR, where it sets VAR), `install` (each package it installs + is this repository's, at its version), `data` (it needs files the repository does not hold), `account` (it needs what only the person has: an account, a device, a program of their own), `server` (it starts a server that does not return), `usage` (a synopsis). Every @@ -349,6 +350,17 @@ def static_line_checks(cli, block, line, report, pkgs, pys): report.fail(block.where, f"pip install {t}: no Python package of this repository is named {name}") +def workflow_values(text, var): + """Every value a workflow gives VAR: as an `env:` key (`VAR: value`) or in a script + (`VAR=value`, `VAR="value"`, `VAR='value'`), quotes off and blanks collapsed.""" + vals = set() + for m in re.finditer(rf"^\s*{var}:[ \t]*(\S.*?)\s*$", text, re.M): + vals.add(" ".join(m.group(1).strip("'\"").split())) + for m in re.finditer(rf"\b{var}=(\"[^\"]*\"|'[^']*'|\S+)", text): + vals.add(" ".join(m.group(1).strip("'\"").split())) + return vals + + def check_norun(cli, block, report, pkgs, pys): reason = block.attrs.get("norun", "") kind, _, detail = reason.partition(":") @@ -368,14 +380,23 @@ def check_norun(cli, block, report, pkgs, pys): if not detail or not wf.exists(): report.fail(block.where, f"norun=ci:{detail}: no such workflow") return - flow = " ".join(wf.read_text(encoding="utf-8").split()) + raw = wf.read_text(encoding="utf-8") + flow = " ".join(raw.split()) for line in block.body: line = re.sub(r"\s+#.*$", "", line).strip() if not line or line.startswith("#"): continue for seg in re.split(r"&&|;", line): seg = seg.strip().strip("()").strip().rstrip("&").strip() - seg = re.sub(r"^([A-Z_][A-Z0-9_]*=\S*\s+)+", "", seg) + # A leading VAR=value (quoted or not) is not part of the command. When the + # workflow sets VAR, the value must be one it gives VAR, so a documented + # setting, such as the wasm runner and its flags, cannot drift from CI's. + while m := re.match(r"([A-Z_][A-Z0-9_]*)=('[^']*'|\"[^\"]*\"|\S*)\s+", seg): + var, val = m.group(1), " ".join(m.group(2).strip("'\"").split()) + sets = workflow_values(raw, var) + if sets and val not in sets: + report.fail(block.where, f"{var}={val}: the workflow {detail} sets it to " + " or ".join(f"`{s}`" for s in sorted(sets))) + seg = seg[m.end():] if not seg or seg.startswith("cd "): continue # A placeholder (PATH/TO/PKG, FILE, $VAR, ) stands for any one word. diff --git a/wai/wai-quantum/README.md b/wai/wai-quantum/README.md index 46cbd376a..542c44253 100644 --- a/wai/wai-quantum/README.md +++ b/wai/wai-quantum/README.md @@ -46,7 +46,7 @@ fails the suite instead of quietly returning a different number (`.github/workflows/wai-conformance.yml`, its `quantum-wasip2` job): ```sh norun=ci:wai-conformance.yml -CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime cargo test --locked --target wasm32-wasip2 --features full --test bit_identity +CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' cargo test --locked --target wasm32-wasip2 --features full --test bit_identity ``` diff --git a/wai/wai-quantum/README.zh-CN.md b/wai/wai-quantum/README.zh-CN.md index 6412c92d7..72a2b3b5c 100644 --- a/wai/wai-quantum/README.zh-CN.md +++ b/wai/wai-quantum/README.zh-CN.md @@ -30,7 +30,7 @@ let h: [u8; 32] = sv.statevector_hash(); **跨架构可复现性是经过测试的,而不是仅凭声称。** CI 在 wasmtime 上的 `wasm32-wasip2` 下以及原生环境中,运行固定到精确 IEEE-754 位模式的浮点结果(熵、Holevo 界)以及线路核心的单元测试;因此,一个计算结果不同的平台会让测试套件失败,而不是悄悄返回另一个数字(`.github/workflows/wai-conformance.yml` 中的 `quantum-wasip2` 作业): ```sh norun=ci:wai-conformance.yml -CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime cargo test --locked --target wasm32-wasip2 --features full --test bit_identity +CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' cargo test --locked --target wasm32-wasip2 --features full --test bit_identity ``` diff --git a/wai/wai-quantum/tests/bit_identity.rs b/wai/wai-quantum/tests/bit_identity.rs index c295de9bc..fee481b64 100644 --- a/wai/wai-quantum/tests/bit_identity.rs +++ b/wai/wai-quantum/tests/bit_identity.rs @@ -4,7 +4,7 @@ //! natively and confirmed under `wasm32-wasip2`; any platform whose math //! differs fails here, not in a user's diff. //! -//! `CARGO_TARGET_WASM32_WASIP2_RUNNER=wasmtime cargo test --release --target wasm32-wasip2 --features full` +//! `CARGO_TARGET_WASM32_WASIP2_RUNNER='wasmtime run -W nan-canonicalization=y -W relaxed-simd-deterministic=y' cargo test --release --target wasm32-wasip2 --features full` #![cfg(all( feature = "quantum", feature = "quantum_vml", -- 2.54.0 (Apple Git-157) From d7ab19292f768b429297f47ebc9d42221af1e1b6 Mon Sep 17 00:00:00 2001 From: Transaction Science Date: Wed, 4 Nov 2026 02:58:54 -0500 Subject: [PATCH 11/29] byte-equality: the disk check writes as well as measures, and covers the conformance matrix too The disk pre-check read df under the leg's four directories (the workspace, the runner's temporary directory, TMPDIR and HOME). Three gaps remained. 1. On linux-aarch64 df reads the runner VM's disk, not the host's. Run in a job container started as the runner starts one (its image and its two cache mounts), the step's loop put all four directories on the VM's data disk (84 GiB free), while the host volume that holds the disk image had 50 GiB. The image is a 200 GiB file that was already allocated on the host but for 0.7 GiB, so a full host volume could take back at most that much. The step now also writes and syncs 32 MiB once on each filesystem it measures (dd conv=fsync, which the macOS host's dd and the job image's both take), and names a write refused for space, an I/O error, a read-only filesystem or a quota infrastructure. That also catches a disk out of inodes, which df's block count does not show. Another failed write is a warning, not a verdict. 2. The second determinism matrix, wai-conformance.yml's byte-exact job, runs on the same three labels with no disk check. It now has both steps, with the same floor. 3. The floor had no reading behind it. The step's loop, run on 2026-11-04 in each label's job environment: linux-x86_64 201 GiB (one host) and 304 GiB (the other); linux-aarch64 84 GiB; macos 78 GiB under the workspace and the runner's temporary directory, 85 GiB under TMPDIR and HOME. The floor stays at 4 GiB: it fails a leg only on a host within a few GiB of full, and the macOS runner's internal disk, where HOME and TMPDIR sit, has gone from 7 GiB free to 118 MiB within hours, with its jobs failing under HOME. The env comment says so. The class of the failure is still not in the status context: the forge reports `wai cross-architecture byte-equality /