A new capability, wai.quantum.wasmq (extensions/quantum-wasmq.md, its WIT beside it): one WAI1 envelope carries an OpenQASM 3 program, the WebAssembly modules that define its `extern` functions and their priors, each pinned by BLAKE3 in a canonical contract. - The profile: a core Lime1 module that imports nothing, bounds its memory and has no start function, threads, vectors, references or growing table, each rule a refusal code. - The prepared module meters its own fuel at the start of each run of instructions, counts its own call depth and returns only canonical NaNs, so any engine runs it alike. The fuel equals the pinned wasmtime's own default count. - Two engines, the pinned wasmtime and an interpreter of the crate's own that also runs on wasm32, agree on every instruction, charge, trap and fault. - The reader takes extern calls as steps when asked (qasm::read_callouts); its existing types are unchanged. - A run seals the engine's job receipt with the fuel, the modules' digests, a digest of every call and the fault; a callout over budget ends in a signed fault receipt. - wai-quantum pack-wasmq, check-wasmq, run ENVELOPE.wai (--engine, --transcript, --replay) and verify --bundle. - Reference modules from the engine's own decoder and atom planner, modules of record built on x86_64 Linux; vectors in quantum-conformance/wasmq/; a workflow that runs them on linux, macOS and wasm32, and rebuilds the modules. Co-Authored-By: Claude Opus 5.5 <[email protected]>
171 lines
7.8 KiB
Python
Executable file
171 lines
7.8 KiB
Python
Executable file
#!/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_<triple>_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;
|
|
5. every crate under wai/ that depends on wasmtime as a library pins it to that release
|
|
exactly (`version = "=PIN"`), so the engine a crate embeds is the one the workflows
|
|
run (wai-wasmq's native engine, extensions/quantum-wasmq.md §4.6).
|
|
|
|
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")
|
|
# A Cargo dependency on the wasmtime library, and the version it asks for.
|
|
CRATE_DEP = re.compile(r"^\s*wasmtime\s*=")
|
|
CRATE_VERSION = re.compile(r"version\s*=\s*\"([^\"]+)\"|^\s*wasmtime\s*=\s*\"([^\"]+)\"")
|
|
|
|
|
|
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
|
|
crates = 0
|
|
wai = os.path.join(root, "wai")
|
|
for dirpath, dirnames, filenames in os.walk(wai):
|
|
dirnames[:] = [d for d in dirnames if d not in ("target", "node_modules", ".git")]
|
|
if "Cargo.toml" not in filenames:
|
|
continue
|
|
path = os.path.join(dirpath, "Cargo.toml")
|
|
rel = os.path.relpath(path, root)
|
|
section = ""
|
|
for n, line in enumerate(open(path, encoding="utf-8").read().splitlines(), 1):
|
|
if line.strip().startswith("["):
|
|
section = line.strip()
|
|
continue
|
|
# Only a dependency table names a crate; `wasmtime = [...]` in [features] does not.
|
|
if "dependencies" not in section or not CRATE_DEP.match(line):
|
|
continue
|
|
crates += 1
|
|
v = CRATE_VERSION.search(line)
|
|
ver = (v.group(1) or v.group(2)) if v else None
|
|
if ver != "=" + PIN:
|
|
findings.append(f"{rel}:{n}: depends on wasmtime {ver or '(no version)'}; a crate pins the release the workflows run, \"={PIN}\"")
|
|
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; {crates} crate dependencies, each on ={PIN}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv))
|