Wave A: public-name check, WebAssembly determinism in CI, decode-path reporting, and pin by default #616
109 changed files with 9590 additions and 426 deletions
13
.github/workflows/repo-checks.yml
vendored
13
.github/workflows/repo-checks.yml
vendored
|
|
@ -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() }}
|
||||
|
|
@ -108,6 +116,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)"
|
||||
|
|
|
|||
54
.github/workflows/wai-breaking.yml
vendored
54
.github/workflows/wai-breaking.yml
vendored
|
|
@ -11,9 +11,13 @@ name: WAI breaking changes and the conformance runner
|
|||
# the room service's OpenAPI document).
|
||||
# - runner: the tests of `wai breaking` (each kind of break reported and failing until a
|
||||
# changelog entry names it) and of `wai conform` (the reference implementations conform, a
|
||||
# changed verdict or output byte fails, a changed record is refused by `wai verify`); then
|
||||
# `wai conform` runs the corpora's independent Python verifiers and the reference packager,
|
||||
# and `wai verify` checks every record those runs wrote.
|
||||
# changed verdict or output byte fails, a changed record is refused by `wai verify`, and
|
||||
# `wai verify --conformant` admits a passing run of the native sink and refuses a planted
|
||||
# failing one); then `wai conform` runs the corpora's independent Python verifiers and the
|
||||
# reference packager, and `wai verify` checks every record those runs wrote; and it runs the
|
||||
# native sink, through its module and its C interface, and the score decoder, each record
|
||||
# admitted against this checkout's corpus and a planted failing sink refused
|
||||
# (conformance-results §4.1).
|
||||
#
|
||||
# Push filters match the push's delta, and the `relevant` job gates the work from the pull
|
||||
# request's own diff (scripts/pr-touches-paths.py, reading the list below), as wai-pack.yml
|
||||
|
|
@ -128,15 +132,18 @@ jobs:
|
|||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
git config --global user.email [email protected]
|
||||
git config --global user.name ci
|
||||
cargo test --locked --no-default-features --features quickstart,score,key_discovery \
|
||||
--bin wai --test cli_breaking --test cli_conform
|
||||
cargo test --locked --no-default-features --features quickstart,score,key_discovery,native_sink \
|
||||
--bin wai --test cli_breaking --test cli_conform --test cli_conform_sinks
|
||||
- name: Build wai and the reference packager
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
out() { (cd "$1" && cargo metadata --format-version 1 --no-deps --locked | python3 -c 'import json,sys; print(json.load(sys.stdin)["target_directory"])'); }
|
||||
mkdir -p "$RUNNER_TEMP/bin"
|
||||
(cd wai/wai-rs && cargo build --locked --no-default-features --features quickstart --bin wai) && cp "$(out wai/wai-rs)/debug/wai" "$RUNNER_TEMP/bin/"
|
||||
# The test step's features, so cargo reuses what it built.
|
||||
(cd wai/wai-rs && cargo build --locked --no-default-features --features quickstart,score,key_discovery,native_sink \
|
||||
--bin wai --bin wai_native_sink_vectors)
|
||||
cp "$(out wai/wai-rs)/debug/wai" "$(out wai/wai-rs)/debug/wai_native_sink_vectors" "$RUNNER_TEMP/bin/"
|
||||
(cd wai/wai-pack && cargo build --locked --bin wai-pack) && cp "$(out wai/wai-pack)/debug/wai-pack" "$RUNNER_TEMP/bin/"
|
||||
# The second implementations of the claim, JWP and key-discovery corpora, written from the
|
||||
# extension texts and sharing no code with wai-rs, run through the conformance runner as
|
||||
|
|
@ -156,3 +163,38 @@ jobs:
|
|||
"$w" conform packager --suite policy --text --root wai \
|
||||
--impl "$RUNNER_TEMP/bin/wai-pack policy-verdict {in}" --out "$d/policy.wai-conform.json"
|
||||
"$w" verify "$d"
|
||||
# The native sink through the conformance runner, as another implementation would be run:
|
||||
# through its Rust module and through the C interface its platform integrations link; and
|
||||
# the score decoder. Each run must be conformant, and `wai verify
|
||||
# --conformant` must admit each record against this checkout's corpus. A planted sink that
|
||||
# presents an object whose digest does not match must be refused: its run fails, its record
|
||||
# still verifies, and admission refuses it (not_conformant).
|
||||
- name: wai conform with the native sink and the score decoder, each record admitted, a planted failing sink refused
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
w="$RUNNER_TEMP/bin/wai"; s="$RUNNER_TEMP/bin/wai_native_sink_vectors"; d="$RUNNER_TEMP/sinks"; mkdir -p "$d"
|
||||
"$w" conform sink --suite native-sink --text --root wai --impl "$s answers {case}" --out "$d/native-sink.wai-conform.json"
|
||||
"$w" conform sink --suite native-sink --text --root wai --impl "$s answers --c-abi {case}" --out "$d/native-sink-c-abi.wai-conform.json"
|
||||
"$w" conform decoder --suite score --text --root wai --impl "$w score render {in} --pcm {out}" --out "$d/score.wai-conform.json"
|
||||
"$w" verify --conformant --root wai "$d"
|
||||
# The planted sink's run must fail as a run (exit 1), not as a usage error, a missing
|
||||
# program or an adapter that does not answer, and on the case it tampers with; its
|
||||
# record must be refused at rule 2, not at another.
|
||||
p="$RUNNER_TEMP/planted"; mkdir -p "$p"
|
||||
planted="$s answers {case} | sed '/object_digest_mismatch/s/\"present\":false/\"present\":true/'"
|
||||
set +e
|
||||
"$w" conform sink --suite native-sink --text --root wai --impl "$planted" --out "$p/planted.wai-conform.json" > "$RUNNER_TEMP/planted-run.txt" 2>&1
|
||||
c=$?
|
||||
set -e
|
||||
cat "$RUNNER_TEMP/planted-run.txt"
|
||||
[ "$c" = 1 ] || { echo "the planted failing sink: wai conform exited $c, not 1"; exit 1; }
|
||||
grep -q 'tampered-fragment' "$RUNNER_TEMP/planted-run.txt" || { echo "the planted failing sink: tampered-fragment is not a failed case"; exit 1; }
|
||||
"$w" verify "$p"
|
||||
set +e
|
||||
"$w" verify --conformant --root wai "$p" > "$RUNNER_TEMP/planted-admit.txt" 2>&1
|
||||
c=$?
|
||||
set -e
|
||||
cat "$RUNNER_TEMP/planted-admit.txt"
|
||||
[ "$c" = 1 ] || { echo "the planted failing sink: wai verify --conformant exited $c, not 1"; exit 1; }
|
||||
grep -q 'FAIL admission rule 2 not_conformant' "$RUNNER_TEMP/planted-admit.txt" || { echo "the planted failing sink was not refused at rule 2"; exit 1; }
|
||||
|
|
|
|||
97
.github/workflows/wai-byte-equality.yml
vendored
97
.github/workflows/wai-byte-equality.yml
vendored
|
|
@ -164,11 +164,74 @@ 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. Measured with the
|
||||
# step's own loop on 2026-11-04, every label had 50 GiB or more free under all
|
||||
# four directories, so the floor fails a leg only on a host within a few GiB of
|
||||
# full. The macOS runner's HOME and TMPDIR sit on its internal disk, which has
|
||||
# gone from 7 GiB free to 118 MiB within hours, and at that level its jobs failed
|
||||
# writing under HOME.
|
||||
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), and writes and syncs 32 MiB once on each filesystem, which a
|
||||
# disk out of inodes or remounted read-only fails though df shows room. Below the
|
||||
# floor, or on a write refused for space, an I/O error or a read-only filesystem,
|
||||
# it fails, and its name and its error say infrastructure.
|
||||
#
|
||||
# On linux-aarch64 the four directories are on the runner VM's data disk, so df
|
||||
# reads the guest's free space, not the host volume's. The disk is an image file
|
||||
# on that volume, and on 2026-11-04 all but 0.7 of its 200 GiB were already
|
||||
# allocated there, so a full host volume could take back at most that much; the
|
||||
# write probe catches the rest.
|
||||
#
|
||||
# The forge still reports the leg's own status context as failed (`wai
|
||||
# cross-architecture byte-equality / <label>`), as for a divergence; the class is
|
||||
# in the failed step's name and its error. A job of its own per label would put it
|
||||
# in the context, but the `linux` label has two hosts, so that job could measure
|
||||
# another host's disk than the leg's.
|
||||
- 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; seen=""
|
||||
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; continue
|
||||
fi
|
||||
case " $seen " in *" $2 "*) continue ;; esac
|
||||
seen="$seen $2"; f="$p/.wai-disk-probe.$$"
|
||||
if ! err=$(dd if=/dev/zero of="$f" bs=1048576 count=32 conv=fsync 2>&1); then
|
||||
case "$err" in
|
||||
*"No space left"*|*"Input/output error"*|*"Read-only file system"*|*"quota"*)
|
||||
echo "::error::infrastructure: ${{ matrix.label }}: a 32 MiB write under $p, on $2, failed: $(printf '%s' "$err" | head -n 1). The runner host's disk cannot take the leg's writes: no fixture was decoded, so this is not a byte-equality failure."
|
||||
short=1 ;;
|
||||
*) echo "::warning::a 32 MiB write under $p failed for another reason: $(printf '%s' "$err" | head -n 1)" ;;
|
||||
esac
|
||||
fi
|
||||
rm -f "$f"
|
||||
done
|
||||
exit $short
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -195,3 +258,37 @@ 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 and write are made again: below the
|
||||
# floor, or on a write refused for space, 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; seen=""
|
||||
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; continue
|
||||
fi
|
||||
case " $seen " in *" $2 "*) continue ;; esac
|
||||
seen="$seen $2"; f="$p/.wai-disk-probe.$$"
|
||||
if ! err=$(dd if=/dev/zero of="$f" bs=1048576 count=32 conv=fsync 2>&1); then
|
||||
case "$err" in
|
||||
*"No space left"*|*"Input/output error"*|*"Read-only file system"*|*"quota"*)
|
||||
echo "::error::infrastructure: ${{ matrix.label }}: a 32 MiB write under $p, on $2, failed: $(printf '%s' "$err" | head -n 1). 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 ;;
|
||||
*) echo "::warning::a 32 MiB write under $p failed for another reason: $(printf '%s' "$err" | head -n 1)" ;;
|
||||
esac
|
||||
fi
|
||||
rm -f "$f"
|
||||
done
|
||||
[ "$short" = 1 ] || echo "Every disk this leg writes to has room: the failure above is not the disk."
|
||||
exit $short
|
||||
|
|
|
|||
119
.github/workflows/wai-conformance.yml
vendored
119
.github/workflows/wai-conformance.yml
vendored
|
|
@ -333,6 +333,11 @@ on:
|
|||
- 'wai/research/determinism-cost/**'
|
||||
- 'wai/tools/wai_determinism_cost_verify.py'
|
||||
- 'wai/extensions/determinism-tiers.md'
|
||||
# The catalogue-prior record, its verifier, and the page whose quoted figures the
|
||||
# verifier checks against the readings.
|
||||
- 'wai/research/catalogue-prior.md'
|
||||
- 'wai/research/catalogue-prior/**'
|
||||
- 'wai/tools/wai_catalogue_prior_verify.py'
|
||||
# tests/stream_termination.rs requires this extension's §7 to list the codes the reference
|
||||
# reports and capability-constraints §4.2 to name the match's bits,
|
||||
# stream_termination_verify.py reads both, and the registry's wire identifiers test reads
|
||||
|
|
@ -969,7 +974,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -979,18 +992,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 +1013,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 +1096,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 +1126,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -1123,11 +1144,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 +1162,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; }
|
||||
|
||||
|
|
@ -1318,6 +1339,17 @@ jobs:
|
|||
python3 -I tools/wai_determinism_cost_verify.py research/determinism-cost/record.json \
|
||||
research/determinism-cost.md extensions/determinism-tiers.md
|
||||
(cd wai-rs && cargo test --no-default-features --features "$WAI_FEATURES" --test det_cost_bin)
|
||||
# The catalogue-prior record (research/catalogue-prior.md): each reading checked against
|
||||
# the SHA-256 its run.json states; every BD-rate the run stored recomputed from the
|
||||
# rate and quality points, and the rate proxy, the recurrence and the decision rule's
|
||||
# verdicts from the readings, by a second implementation that shares no code with the
|
||||
# scripts that took them (tools/wai_catalogue_prior_verify.py, standard library only,
|
||||
# run isolated); and every figure the page quotes checked (each is followed by a `cat`
|
||||
# marker naming its value).
|
||||
- name: catalogue prior · the record and the text quoting it
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: python3 -I tools/wai_catalogue_prior_verify.py research/catalogue-prior research/catalogue-prior.md
|
||||
# The staged-delivery corpus's second implementation (staged-conformance/README): it
|
||||
# replays every case from its stage-0 buffers through its own reading of the
|
||||
# extension (refines, WIR1, the sink's steps, holds, batches, conflicts, deadlines,
|
||||
|
|
@ -2903,8 +2935,46 @@ jobs:
|
|||
# would add no arch coverage anyway.
|
||||
# Windows x86_64 leg removed 2026-09-30: runner offline. Restore by
|
||||
# re-adding `windows` here once the runner is back online.
|
||||
# The two disk steps below are bash, and skip on Windows.
|
||||
os: [linux, linux-arm64, macos]
|
||||
env:
|
||||
# The floor of the disk check below, in GiB, as in wai-byte-equality.yml, whose
|
||||
# comment gives the leg's writes and the readings behind it.
|
||||
DISK_FLOOR_GIB: '4'
|
||||
steps:
|
||||
# The two disk steps of wai-byte-equality.yml's legs, on the same three labels:
|
||||
# a full runner disk fails here as infrastructure, before the checkout, and a
|
||||
# failure later in the leg is named infrastructure when the disk filled meanwhile.
|
||||
# wai-byte-equality.yml's comment says what they measure and why.
|
||||
- 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; seen=""
|
||||
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.os }}: $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; continue
|
||||
fi
|
||||
case " $seen " in *" $2 "*) continue ;; esac
|
||||
seen="$seen $2"; f="$p/.wai-disk-probe.$$"
|
||||
if ! err=$(dd if=/dev/zero of="$f" bs=1048576 count=32 conv=fsync 2>&1); then
|
||||
case "$err" in
|
||||
*"No space left"*|*"Input/output error"*|*"Read-only file system"*|*"quota"*)
|
||||
echo "::error::infrastructure: ${{ matrix.os }}: a 32 MiB write under $p, on $2, failed: $(printf '%s' "$err" | head -n 1). The runner host's disk cannot take the leg's writes: no fixture was decoded, so this is not a byte-equality failure."
|
||||
short=1 ;;
|
||||
*) echo "::warning::a 32 MiB write under $p failed for another reason: $(printf '%s' "$err" | head -n 1)" ;;
|
||||
esac
|
||||
fi
|
||||
rm -f "$f"
|
||||
done
|
||||
exit $short
|
||||
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install Rust 1.98.0 (unix)
|
||||
|
|
@ -2928,6 +2998,35 @@ jobs:
|
|||
working-directory: wai/byte-exact-conformance
|
||||
run: cargo test --release
|
||||
|
||||
- name: "infrastructure: was the disk full when the leg failed?"
|
||||
if: failure() && steps.disk.outcome == 'success'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
short=0; seen=""
|
||||
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.os }}: $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; continue
|
||||
fi
|
||||
case " $seen " in *" $2 "*) continue ;; esac
|
||||
seen="$seen $2"; f="$p/.wai-disk-probe.$$"
|
||||
if ! err=$(dd if=/dev/zero of="$f" bs=1048576 count=32 conv=fsync 2>&1); then
|
||||
case "$err" in
|
||||
*"No space left"*|*"Input/output error"*|*"Read-only file system"*|*"quota"*)
|
||||
echo "::error::infrastructure: ${{ matrix.os }}: a 32 MiB write under $p, on $2, failed: $(printf '%s' "$err" | head -n 1). 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 ;;
|
||||
*) echo "::warning::a 32 MiB write under $p failed for another reason: $(printf '%s' "$err" | head -n 1)" ;;
|
||||
esac
|
||||
fi
|
||||
rm -f "$f"
|
||||
done
|
||||
[ "$short" = 1 ] || echo "Every disk this leg writes to has room: the failure above is not the disk."
|
||||
exit $short
|
||||
|
||||
# The codec wrappers are behind `codecs`, which binds C and C++ libraries that no
|
||||
# container image of this fleet carries, so no other job compiles them. A feature no
|
||||
# workflow enables has no CI coverage at all: without this job the wrappers' tests,
|
||||
|
|
|
|||
93
.github/workflows/wai-neural-gates.yml
vendored
93
.github/workflows/wai-neural-gates.yml
vendored
|
|
@ -28,6 +28,14 @@ name: WAI neural sink gates
|
|||
# registry given a key log refuses the committed int_mlicpp set once the log revokes
|
||||
# its digest, and when the sink holds no fresh status, and decodes it while a grant
|
||||
# holds it.
|
||||
#
|
||||
# The dispatch reports the decode path that ran (determinism-tiers §5). The tests
|
||||
# with one path run on committed inputs in the first job. A second job fetches the
|
||||
# pinned jpegai/v1 bundle and runs wai.image.jpegai's exact decoder through the
|
||||
# dispatch, with WAI_REQUIRE_SOTA_VECTORS=1, so a missing input is a failure. The
|
||||
# float decode needs the JPEG-AI reference-software assets, which are not in the
|
||||
# repository, so its test runs locally only, as tests/jpegai_live.rs does
|
||||
# (wai-jpegai-e2e.yml).
|
||||
|
||||
# One run per branch at a time. A push supersedes the run before it.
|
||||
#
|
||||
|
|
@ -66,6 +74,19 @@ on:
|
|||
- 'wai/wai-rs/src/ffi_neural.rs'
|
||||
- 'wai/wai-rs/tests/ffi_neural_pin.rs'
|
||||
- 'wai/wai-rs/tests/neural_cost_limit.rs'
|
||||
- 'wai/wai-rs/tests/jpegai_decode_path.rs'
|
||||
# The exact JPEG-AI decoder the second job runs through the dispatch, and the
|
||||
# fetch of its pinned inputs.
|
||||
- 'wai/wai-rs/src/jpegai_*.rs'
|
||||
- 'wai/wai-rs/src/float_repro.rs'
|
||||
- 'wai/wai-rs/src/int_kernels.rs'
|
||||
- 'wai/tools/wai_jpegai_fetch_e2e_inputs.sh'
|
||||
- 'wai/tools/jpegai-e2e-v1.sha256'
|
||||
- 'wai/wai-rs/tests/vectors/jpegai/**'
|
||||
- 'wai/wai-rs/tests/support/**'
|
||||
- 'wai/wai-rs/src/session_recorder.rs'
|
||||
- 'wai/wai-rs/src/reconstruction_binding.rs'
|
||||
- 'wai/wai-rs/src/claim_receipt/**'
|
||||
- 'wai/wai-rs/src/int_synth.rs'
|
||||
# The integer operator set the integer decoders load their sets through.
|
||||
- 'wai/wai-rs/src/int_ops.rs'
|
||||
|
|
@ -187,6 +208,22 @@ jobs:
|
|||
cargo test --no-default-features --features neural,ffi,video_int,audio_int,splat_int --test neural_cost_limit \
|
||||
2>&1 | tee "$RUNNER_TEMP/neural-cost.log"
|
||||
grep -q "test result: ok. 3 passed; 0 failed" "$RUNNER_TEMP/neural-cost.log"
|
||||
# The dispatch reports the capability it decoded through and the path that ran
|
||||
# (extensions/determinism-tiers.md §5): a capability with one path says so, through
|
||||
# its own capability and through its fallback, and a reconstruction binding over a
|
||||
# decode-equivalence capability is checked under its descriptor. Committed inputs
|
||||
# only. The exact JPEG-AI decoder runs in the next job; the float decode runs locally
|
||||
# only (see the header).
|
||||
- name: The neural dispatch reports the decode path that ran
|
||||
working-directory: wai/wai-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo test --no-default-features --features neural,receipt_claims,video_int --test jpegai_decode_path -- \
|
||||
--exact a_capability_with_one_path_reports_it_and_the_capability_it_decoded_through \
|
||||
a_binding_over_a_decode_equivalence_capability_is_checked_under_its_descriptor \
|
||||
2>&1 | tee "$RUNNER_TEMP/neural-path.log"
|
||||
grep -q "test result: ok. 2 passed; 0 failed" "$RUNNER_TEMP/neural-path.log"
|
||||
- name: A key log refuses a revoked pinned set at the neural sink (SPEC §3.1 check 5)
|
||||
working-directory: wai/wai-rs
|
||||
shell: bash
|
||||
|
|
@ -223,5 +260,59 @@ jobs:
|
|||
cargo test --no-default-features --features neural,ffi --test ffi_neural_pin -- \
|
||||
--exact tampered_model_is_wai_err_prior strict_replicate_is_wai_err_unsound_replicate \
|
||||
wai2_decode_ex_picks_the_held_pin failed_decode_ex_clears_out \
|
||||
decode_ex_reports_the_path_and_the_fallback \
|
||||
2>&1 | tee "$RUNNER_TEMP/neural-ffi.log"
|
||||
grep -q "test result: ok. 4 passed; 0 failed" "$RUNNER_TEMP/neural-ffi.log"
|
||||
grep -q "test result: ok. 5 passed; 0 failed" "$RUNNER_TEMP/neural-ffi.log"
|
||||
|
||||
# wai.image.jpegai's exact decoder through the dispatch: the result says
|
||||
# DecodePath::Exact, a session claim recorded from it states `variant` `exact` and
|
||||
# verifies, and the binding check holds over its pixels, differs over a changed one,
|
||||
# and reports a binding of another decoderId as another reconstruction
|
||||
# (tests/jpegai_decode_path.rs). Its inputs are the pinned jpegai/v1 bundle, fetched
|
||||
# and checked against wai/tools/jpegai-e2e-v1.sha256 as wai-jpegai-e2e.yml does; an
|
||||
# outage of the models root fails the job. The `neural` feature links the prebuilt
|
||||
# ONNX Runtime, so this job runs in the 24.04 image, as the job above does.
|
||||
jpegai-decode-path:
|
||||
needs: relevant
|
||||
if: needs.relevant.outputs.run == 'true'
|
||||
name: the exact JPEG-AI decoder reports its path (pinned inputs, required)
|
||||
runs-on: linux
|
||||
timeout-minutes: 60
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-24.04
|
||||
env:
|
||||
WAI_REQUIRE_SOTA_VECTORS: '1'
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
# Bootstrap rustup: the docker runner image ships no Rust.
|
||||
- name: Install Rust 1.98.0
|
||||
run: |
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
command -v rustup >/dev/null 2>&1 || \
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
|
||||
rustup toolchain install 1.98.0 --profile minimal
|
||||
rustup default 1.98.0
|
||||
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||
- name: Fetch the JPEG-AI inputs and check each against the pinned manifest
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: tools/wai_jpegai_fetch_e2e_inputs.sh "$RUNNER_TEMP/jpegai_e2e"
|
||||
- name: The exact decoder reports its path, in the result, the claim and the binding check (missing input = failure)
|
||||
working-directory: wai/wai-rs
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
[ "${WAI_REQUIRE_SOTA_VECTORS:-}" = 1 ] \
|
||||
|| { echo "::error::WAI_REQUIRE_SOTA_VECTORS must be 1 in this job"; exit 1; }
|
||||
export WAI_JPEGAI_WEIGHTS_DIR="$RUNNER_TEMP/jpegai_e2e"
|
||||
export WAI_JPEGAI_NORM="$RUNNER_TEMP/jpegai_e2e/norm.json"
|
||||
cargo test --release --no-default-features --features neural,receipt_claims,video_int --test jpegai_decode_path -- \
|
||||
--exact the_jpegai_exact_decoder_reports_itself --show-output 2>&1 | tee "$RUNNER_TEMP/jpegai-path.log"
|
||||
grep -q "test result: ok. 1 passed; 0 failed" "$RUNNER_TEMP/jpegai-path.log"
|
||||
grep -q "^test the_jpegai_exact_decoder_reports_itself \.\.\. ok$" "$RUNNER_TEMP/jpegai-path.log"
|
||||
# A skip also prints `test X ... ok`; every skip writes a banner with
|
||||
# "SKIPPED" and "NOT VERIFIED" (tests/support/require_vectors.rs).
|
||||
if grep -nE 'SKIPPED|NOT VERIFIED' "$RUNNER_TEMP/jpegai-path.log"; then
|
||||
echo "::error::the exact JPEG-AI decode skipped instead of checking its input"
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
31
.github/workflows/wai-ops.yml
vendored
31
.github/workflows/wai-ops.yml
vendored
|
|
@ -207,14 +207,31 @@ 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 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: |
|
||||
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 +242,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 +256,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
|
||||
|
|
|
|||
24
.github/workflows/wai-quantum-cli.yml
vendored
24
.github/workflows/wai-quantum-cli.yml
vendored
|
|
@ -111,7 +111,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -121,11 +129,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 +150,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 +174,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"
|
||||
|
||||
|
|
|
|||
18
.github/workflows/wai-quantum-formats.yml
vendored
18
.github/workflows/wai-quantum-formats.yml
vendored
|
|
@ -129,7 +129,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -139,11 +147,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 +161,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 +173,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
|
||||
|
|
|
|||
16
.github/workflows/wai-quantum-lang.yml
vendored
16
.github/workflows/wai-quantum-lang.yml
vendored
|
|
@ -116,7 +116,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -126,11 +134,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 +148,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"
|
||||
|
|
|
|||
16
.github/workflows/wai-quantum-pulse.yml
vendored
16
.github/workflows/wai-quantum-pulse.yml
vendored
|
|
@ -172,7 +172,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -182,11 +190,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 +202,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"
|
||||
|
|
|
|||
16
.github/workflows/wai-quantum-registry.yml
vendored
16
.github/workflows/wai-quantum-registry.yml
vendored
|
|
@ -140,7 +140,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -150,11 +158,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 +170,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"
|
||||
|
|
|
|||
18
.github/workflows/wai-quantum-verdict.yml
vendored
18
.github/workflows/wai-quantum-verdict.yml
vendored
|
|
@ -135,7 +135,15 @@ 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 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: |
|
||||
set -euo pipefail
|
||||
|
|
@ -145,11 +153,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 +167,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 +178,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"
|
||||
|
|
|
|||
31
.github/workflows/wai-vector.yml
vendored
31
.github/workflows/wai-vector.yml
vendored
|
|
@ -178,14 +178,31 @@ 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 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: |
|
||||
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 +213,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 +227,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
|
||||
|
|
|
|||
50
.github/workflows/wai-web-wasm.yml
vendored
50
.github/workflows/wai-web-wasm.yml
vendored
|
|
@ -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
|
||||
|
|
@ -244,7 +290,7 @@ jobs:
|
|||
run: |
|
||||
set -euo pipefail
|
||||
cargo test --locked --lib 2>&1 | tee "$RUNNER_TEMP/wai-web-native.log"
|
||||
grep -q "test result: ok. 36 passed; 0 failed" "$RUNNER_TEMP/wai-web-native.log"
|
||||
grep -q "test result: ok. 37 passed; 0 failed" "$RUNNER_TEMP/wai-web-native.log"
|
||||
- name: wasm32 int_synth decode equals the frozen native corpus
|
||||
shell: bash
|
||||
working-directory: wai/wai-web
|
||||
|
|
|
|||
|
|
@ -382,9 +382,11 @@ impl Index {
|
|||
pub struct DvrStats {
|
||||
pub groups_written: AtomicU64,
|
||||
pub objects_written: AtomicU64,
|
||||
/// Groups stored that left the window, by age or by its byte budget: each media group
|
||||
/// and each receipts group, counted as it goes.
|
||||
pub groups_evicted: AtomicU64,
|
||||
/// Groups that became whole after the window had moved past them (their receipts or
|
||||
/// media group already evicted): not stored.
|
||||
/// media group already evicted): not stored, so never counted as evicted.
|
||||
pub groups_late: AtomicU64,
|
||||
/// Objects dropped because their check or their receipt did not verify.
|
||||
pub objects_dropped: AtomicU64,
|
||||
|
|
@ -2050,9 +2052,11 @@ impl Dvr {
|
|||
if let Some(r) = room {
|
||||
ix.series.entry(r).or_default().change(now_us(), -(grp.bytes as i128), 0, 0, 0);
|
||||
}
|
||||
// Counted under the lock it leaves the index under: whoever finds the
|
||||
// group gone (`groups`, `counts`) finds it counted.
|
||||
self.stats.groups_evicted.fetch_add(1, Ordering::Relaxed);
|
||||
grp.seg
|
||||
};
|
||||
self.stats.groups_evicted.fetch_add(1, Ordering::Relaxed);
|
||||
let open = w.open.get(&k).map(|s| s.seq);
|
||||
self.retire(&k, seg, open);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@
|
|||
//!
|
||||
//! - **The window.** A long publish crosses the window's byte budget: the oldest groups go,
|
||||
//! whole, each media group with its receipts group, and the window holds what it says. A
|
||||
//! window by age empties as its groups pass it.
|
||||
//! window by age empties as its groups pass it, each group counted once: evicted, or late
|
||||
//! when the relay took it whole only after the window had passed it (out of order, as a
|
||||
//! loaded host takes streams).
|
||||
//! - **FETCH inside, across and before the window**, ascending and descending: inside, every
|
||||
//! object, each verifying against the fetched receipts; across, an End of Unknown Range
|
||||
//! for what was evicted and every object after it, from the store and the cache together;
|
||||
|
|
@ -110,13 +112,23 @@ fn bundle_of(key: &SigningKey, receipt_alias: u64, g: u64, payloads: &[Vec<u8>])
|
|||
|
||||
/// Publish group `g` of `t`: its receipts, then its objects on one subgroup stream.
|
||||
async fn publish_multi(c: &Client, key: &SigningKey, t: &Tracks, g: u64, tag: &str) {
|
||||
publish_parts(c, key, t, g, tag, true, true).await;
|
||||
}
|
||||
|
||||
/// Publish group `g` of `t` in part: its receipts (with `receipts`), then its objects on one
|
||||
/// subgroup stream (with `media`).
|
||||
async fn publish_parts(c: &Client, key: &SigningKey, t: &Tracks, g: u64, tag: &str, receipts: bool, media: bool) {
|
||||
let payloads: Vec<Vec<u8>> = (0..OBJECTS).map(|o| object_payload(tag, g, o)).collect();
|
||||
let bundle = bundle_of(key, t.receipt_alias, g, &payloads);
|
||||
let rh = SubgroupHeader { track_alias: t.receipts_alias, group_id: g, subgroup_id: SubgroupId::Zero, publisher_priority: Some(t.priority), properties: false, end_of_group: true, first_object: true };
|
||||
c.send_subgroup(rh, &[SubgroupObject::normal(0, bundle.to_payload().unwrap())]).await.expect("the receipts");
|
||||
let mh = SubgroupHeader { track_alias: t.media_alias, group_id: g, subgroup_id: SubgroupId::Zero, publisher_priority: Some(t.priority), properties: false, end_of_group: true, first_object: true };
|
||||
let objects: Vec<SubgroupObject> = payloads.into_iter().enumerate().map(|(o, p)| SubgroupObject::normal(o as u64, p)).collect();
|
||||
c.send_subgroup(mh, &objects).await.expect("the objects");
|
||||
if receipts {
|
||||
let bundle = bundle_of(key, t.receipt_alias, g, &payloads);
|
||||
let rh = SubgroupHeader { track_alias: t.receipts_alias, group_id: g, subgroup_id: SubgroupId::Zero, publisher_priority: Some(t.priority), properties: false, end_of_group: true, first_object: true };
|
||||
c.send_subgroup(rh, &[SubgroupObject::normal(0, bundle.to_payload().unwrap())]).await.expect("the receipts");
|
||||
}
|
||||
if media {
|
||||
let mh = SubgroupHeader { track_alias: t.media_alias, group_id: g, subgroup_id: SubgroupId::Zero, publisher_priority: Some(t.priority), properties: false, end_of_group: true, first_object: true };
|
||||
let objects: Vec<SubgroupObject> = payloads.into_iter().enumerate().map(|(o, p)| SubgroupObject::normal(o as u64, p)).collect();
|
||||
c.send_subgroup(mh, &objects).await.expect("the objects");
|
||||
}
|
||||
}
|
||||
|
||||
fn room_tracks(room: [u8; 32], publisher: &SigningKey) -> Tracks {
|
||||
|
|
@ -276,52 +288,83 @@ async fn window_run() {
|
|||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn a_window_by_age_empties_as_its_groups_pass_it() {
|
||||
timeout(Duration::from_secs(60), async {
|
||||
let dir = store_dir("age");
|
||||
let room = [0x53; 32];
|
||||
let publisher = SigningKey::from_bytes(&[0x54; 32]);
|
||||
let t = room_tracks(room, &publisher);
|
||||
let cfg = dvr_config(&dir, 2, 1 << 30, vec![]);
|
||||
let server = relay(|c| {
|
||||
// About two groups: what aged out of the store is out of the cache too.
|
||||
c.cache.max_bytes = 8 * 1024;
|
||||
c.dvr = Some(cfg.clone());
|
||||
})
|
||||
.await;
|
||||
let (p, _) = connect(&server, Via::WebSocket, ClientOptions::default()).await;
|
||||
let _pub = publish_tracks(&p, &t, None).await;
|
||||
timeout(Duration::from_secs(60), age_run(false)).await.expect("in time");
|
||||
}
|
||||
|
||||
/// The same window when the relay takes the groups whole out of order, as a loaded host does
|
||||
/// (each stream is read by a task of its own): group 0 and group 2's receipts first, then,
|
||||
/// once the window has passed group 2, group 1 and group 2's objects.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn a_window_by_age_passes_groups_taken_out_of_order() {
|
||||
timeout(Duration::from_secs(60), age_run(true)).await.expect("in time");
|
||||
}
|
||||
|
||||
async fn age_run(out_of_order: bool) {
|
||||
let dir = store_dir(if out_of_order { "age-order" } else { "age" });
|
||||
let room = [0x53; 32];
|
||||
let publisher = SigningKey::from_bytes(&[0x54; 32]);
|
||||
let t = room_tracks(room, &publisher);
|
||||
let cfg = dvr_config(&dir, 2, 1 << 30, vec![]);
|
||||
let server = relay(|c| {
|
||||
// About two groups: what aged out of the store is out of the cache too.
|
||||
c.cache.max_bytes = 8 * 1024;
|
||||
c.dvr = Some(cfg.clone());
|
||||
})
|
||||
.await;
|
||||
let (p, _) = connect(&server, Via::WebSocket, ClientOptions::default()).await;
|
||||
let _pub = publish_tracks(&p, &t, None).await;
|
||||
let d = server.relay().dvr().unwrap().clone();
|
||||
let (key, rkey) = ((t.ns.clone(), t.media.clone()), (t.ns.clone(), t.receipts.clone()));
|
||||
let empty = || d.groups(&key).is_empty() && d.groups(&rkey).is_empty();
|
||||
if out_of_order {
|
||||
// Group 0, then group 2's receipts, each taken before the next is sent: all three
|
||||
// are stored.
|
||||
publish_multi(&p, &publisher, &t, 0, "a").await;
|
||||
written(&server, 2).await;
|
||||
publish_parts(&p, &publisher, &t, 2, "a", true, false).await;
|
||||
written(&server, 3).await;
|
||||
assert!(until(Duration::from_secs(10), empty).await, "past max_age_s groups 0 and 2 go");
|
||||
publish_multi(&p, &publisher, &t, 1, "a").await;
|
||||
publish_parts(&p, &publisher, &t, 2, "a", false, true).await;
|
||||
} else {
|
||||
for g in 0..3 {
|
||||
publish_multi(&p, &publisher, &t, g, "a").await;
|
||||
}
|
||||
written(&server, 6).await;
|
||||
let d = server.relay().dvr().unwrap().clone();
|
||||
let (key, rkey) = ((t.ns.clone(), t.media.clone()), (t.ns.clone(), t.receipts.clone()));
|
||||
assert!(until(Duration::from_secs(10), || d.groups(&key).is_empty() && d.groups(&rkey).is_empty()).await, "past max_age_s every group goes");
|
||||
assert_eq!(d.counts().groups_evicted, 6);
|
||||
// The next group, fetched at once: what aged out is unknown, and the group is served.
|
||||
publish_multi(&p, &publisher, &t, 3, "a").await;
|
||||
// The relay has taken group 3 into both tracks before the fetches: taking it may
|
||||
// evict a pair from the cache, and the media and receipts FETCHes are two requests,
|
||||
// so a pair evicted between them would leave media without receipts.
|
||||
let relay = server.relay().clone();
|
||||
assert!(
|
||||
until(STEP, || relay.largest(&key) == Some(Location::new(3, OBJECTS - 1)) && relay.largest(&rkey) == Some(Location::new(3, 0))).await,
|
||||
"the relay took group 3"
|
||||
);
|
||||
let (v, _) = connect(&server, Via::WebSocket, ClientOptions::default()).await;
|
||||
let f = fetch_verified(&v, &t, &publisher, range((0, 0), 3)).await;
|
||||
// What aged out is unknown through the last group neither the store nor the cache
|
||||
// holds; every group after it comes whole.
|
||||
assert_eq!(f.unknown.len(), 1);
|
||||
let k = f.unknown[0];
|
||||
assert!(k.object == u64::MAX && k.group < 3, "{k:?}");
|
||||
let want: Vec<(u64, u64)> = (k.group + 1..=3).flat_map(|g| (0..OBJECTS).map(move |o| (g, o))).collect();
|
||||
assert_eq!(f.objects.keys().copied().collect::<Vec<_>>(), want);
|
||||
server.shutdown().await;
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
})
|
||||
.await
|
||||
.expect("in time");
|
||||
}
|
||||
written(&server, 6).await;
|
||||
assert!(until(Duration::from_secs(10), empty).await, "past max_age_s every group goes");
|
||||
// Each group was stored and then evicted, or was late: whole only once the window had
|
||||
// passed its number, and never stored. Each group stored is counted as it goes, the
|
||||
// receipts group evicted with its media group too.
|
||||
let c = d.counts();
|
||||
assert_eq!(c.groups_evicted + c.groups_late, 6, "every group evicted or late: {c:?}");
|
||||
assert_eq!(c.groups_evicted, c.groups_written, "each group stored is counted as it goes: {c:?}");
|
||||
if out_of_order {
|
||||
// Group 0 and group 2's receipts were stored; group 1 and group 2's objects came after
|
||||
// the window had passed group 2.
|
||||
assert_eq!((c.groups_written, c.groups_late), (3, 3), "{c:?}");
|
||||
}
|
||||
// The next group, fetched at once: what aged out is unknown, and the group is served.
|
||||
publish_multi(&p, &publisher, &t, 3, "a").await;
|
||||
// The relay has taken group 3 into both tracks before the fetches: taking it may
|
||||
// evict a pair from the cache, and the media and receipts FETCHes are two requests,
|
||||
// so a pair evicted between them would leave media without receipts.
|
||||
let relay = server.relay().clone();
|
||||
assert!(
|
||||
until(STEP, || relay.largest(&key) == Some(Location::new(3, OBJECTS - 1)) && relay.largest(&rkey) == Some(Location::new(3, 0))).await,
|
||||
"the relay took group 3"
|
||||
);
|
||||
let (v, _) = connect(&server, Via::WebSocket, ClientOptions::default()).await;
|
||||
let f = fetch_verified(&v, &t, &publisher, range((0, 0), 3)).await;
|
||||
// What aged out is unknown through the last group neither the store nor the cache
|
||||
// holds; every group after it comes whole.
|
||||
assert_eq!(f.unknown.len(), 1);
|
||||
let k = f.unknown[0];
|
||||
assert!(k.object == u64::MAX && k.group < 3, "{k:?}");
|
||||
let want: Vec<(u64, u64)> = (k.group + 1..=3).flat_map(|g| (0..OBJECTS).map(move |o| (g, o))).collect();
|
||||
assert_eq!(f.objects.keys().copied().collect::<Vec<_>>(), want);
|
||||
server.shutdown().await;
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// Rewrite one object of the group record whose frame is at `frame_at` in a segment, in
|
||||
|
|
|
|||
|
|
@ -521,11 +521,17 @@ async function enter(role, given = null) {
|
|||
window.roomTest.role = remoteRoom ? "viewer" : ROLE_OF[role];
|
||||
// A watch room's page enters its co-watch log first: the clock's `join` for it is then on the
|
||||
// clock's events track, which the page subscribes to next, and which a room of viewers alone
|
||||
// has no other record on yet.
|
||||
// has no other record on yet. From that join the page states what it presents in every slot
|
||||
// (co-watch §7.3), so the composition's audio context is made before it: making one waits, on
|
||||
// the page's thread, until the browser has its audio output (the first context in a browser
|
||||
// starts it, about 0.2 s on a loaded test runner; a few ms for the next). The rest of its
|
||||
// entry (begin()) still runs after the join.
|
||||
const made = v.watch && fx.compose ? makeCompositor() : null;
|
||||
if (made) made.openAudio();
|
||||
if (v.watch) await startCoWatch(v, r.body, seed, principal);
|
||||
// A remote desktop room's page publishes nothing but its events: it enters as a viewer does.
|
||||
try {
|
||||
await begin(remoteRoom ? "viewer" : ROLE_OF[role], stream);
|
||||
await begin(remoteRoom ? "viewer" : ROLE_OF[role], stream, made);
|
||||
} catch (e) {
|
||||
// A watch room's page that has no session follows no log and applies no control: it stops
|
||||
// stating, rather than state a presentation it does not keep, and the room takes it for gone.
|
||||
|
|
@ -552,8 +558,18 @@ function constraintsOf(d) {
|
|||
};
|
||||
}
|
||||
|
||||
// The room, the composition and the session, once the page knows who it is.
|
||||
async function begin(role, stream) {
|
||||
// The room's composition, drawn on the page's canvas (./compose.js), made for one entry. One an
|
||||
// earlier entry made, which failed, is closed first: its mix, its drawing and its audio context.
|
||||
function makeCompositor() {
|
||||
if (compositor) compositor.close();
|
||||
compositor = new Compositor(wai, $("canvas"), unhex(fx.compose), { log, bindingOf: (vf) => SideTable.of(vf), onError: fail });
|
||||
return compositor;
|
||||
}
|
||||
|
||||
// The room, the composition and the session, once the page knows who it is. `made`: the
|
||||
// composition its entry made already (a watch room's, its audio context made before its join);
|
||||
// without one, begin() makes it.
|
||||
async function begin(role, stream, made = null) {
|
||||
roomId = unhex(fx.room);
|
||||
t0 = fx.t0_us;
|
||||
const who = $("who");
|
||||
|
|
@ -566,7 +582,7 @@ async function begin(role, stream) {
|
|||
// A remote desktop room (WAI remote-desktop) has no composition: its host's screen is drawn
|
||||
// on a canvas of its own.
|
||||
if (role !== "clock" && fx.compose) {
|
||||
compositor = new Compositor(wai, $("canvas"), unhex(fx.compose), { log, bindingOf: (vf) => SideTable.of(vf), onError: fail });
|
||||
compositor = made || makeCompositor();
|
||||
if (DEBUG_BINDING) setInterval(() => { try { renderStates($("binding-debug"), bindingRows()); } catch (e) { fail(e); } }, 500);
|
||||
await compositor.startAudio(new URL("./worklet.js", import.meta.url));
|
||||
await startTermination();
|
||||
|
|
@ -2126,7 +2142,13 @@ window.roomTest.snapshot = () => ({
|
|||
e2ee: e2ee && { ...state.e2ee, ...e2ee.stats, at: e2ee.at, stopped: e2ee.stopped, member: e2ee.state(), dom: $("e2ee") && !$("e2ee").hidden ? $("e2ee").textContent : null },
|
||||
age: state.age,
|
||||
co: co && co.snapshot(),
|
||||
cowatch: cowatch && { ...cowatch.snapshot(), dom: $("cw-sync") ? $("cw-sync").textContent : null },
|
||||
// `audio_open`: when the composition's audio context was made, on the room clock, and how
|
||||
// long making it held the page's thread, beside the stalls of the page's statement loop.
|
||||
cowatch: cowatch && {
|
||||
...cowatch.snapshot(),
|
||||
audio_open: compositor && compositor.audioOpened ? { at: cowatch.roomTime(compositor.audioOpened.wall_ms), took_us: compositor.audioOpened.took_us } : null,
|
||||
dom: $("cw-sync") ? $("cw-sync").textContent : null,
|
||||
},
|
||||
termination: terminationSnapshot(),
|
||||
});
|
||||
// A shared control of a watch room (co-watch §5): `play`, `pause`, `seek` (to, µs) or `rate`
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export class Compositor {
|
|||
this.waiting = true;
|
||||
requestAnimationFrame(() => {
|
||||
this.waiting = false;
|
||||
if (!this.started) return; // closed while it waited
|
||||
try {
|
||||
this.draw();
|
||||
} catch (e) {
|
||||
|
|
@ -151,8 +152,23 @@ export class Compositor {
|
|||
|
||||
// ------------------------------------------------------------------------------ audio
|
||||
|
||||
/** The audio context the mix plays into, made now if it is not yet. Making one holds the
|
||||
* page's thread until the browser has its audio output, which the first context in a browser
|
||||
* starts: a page that must not stall later (a watch room's, stating twice a slot) makes it
|
||||
* first. `audioOpened`: when it was made (`wall_ms`, the page's `Date.now()`) and how long
|
||||
* making it held the thread (`took_us`). */
|
||||
openAudio() {
|
||||
if (!this.ac) {
|
||||
const wall = Date.now();
|
||||
const t = performance.now();
|
||||
this.ac = new AudioContext({ sampleRate: this.desc.params.sample_rate, latencyHint: "interactive" });
|
||||
this.audioOpened = { wall_ms: wall, took_us: Math.round((performance.now() - t) * 1000) };
|
||||
}
|
||||
return this.ac;
|
||||
}
|
||||
|
||||
async startAudio(url) {
|
||||
this.ac = new AudioContext({ sampleRate: this.desc.params.sample_rate, latencyHint: "interactive" });
|
||||
this.openAudio();
|
||||
await this.ac.audioWorklet.addModule(url);
|
||||
this.worklet = new AudioWorkletNode(this.ac, "room-mix", { outputChannelCount: [2] });
|
||||
this.worklet.port.onmessage = (e) => { this.workletStats = e.data; };
|
||||
|
|
@ -161,6 +177,22 @@ export class Compositor {
|
|||
this.mixTimer = setInterval(() => this.mixOnce(), 20);
|
||||
}
|
||||
|
||||
/** Stop for good, as when the entry it was made for failed and another makes its own: no more
|
||||
* mixing or drawing, its frames released and its audio context closed. */
|
||||
close() {
|
||||
this.started = false;
|
||||
clearInterval(this.mixTimer);
|
||||
this.mixTimer = null;
|
||||
if (this.worklet) {
|
||||
this.worklet.port.onmessage = null;
|
||||
this.worklet.disconnect();
|
||||
this.worklet = null;
|
||||
}
|
||||
for (const vf of this.video.values()) vf.close();
|
||||
this.video.clear();
|
||||
if (this.ac && this.ac.state !== "closed") this.ac.close().catch(() => {});
|
||||
}
|
||||
|
||||
addAudio(principal, audioData) {
|
||||
let v = this.audio.get(principal);
|
||||
if (!v) { v = { chunks: [], channels: audioData.numberOfChannels }; this.audio.set(principal, v); }
|
||||
|
|
|
|||
|
|
@ -277,6 +277,10 @@ const POST_EVENTS = 4;
|
|||
// Times a page taken for gone enters again with nothing of it carried since its last entry
|
||||
// (taken for gone again at once, as by a second stall) before it stops.
|
||||
const REENTRIES = 3;
|
||||
// The player's wait between two passes of its loop, in ms, and the stalls of the loop a page
|
||||
// keeps (`stalls`).
|
||||
const PASS_MS = 50;
|
||||
const STALLS_KEPT = 256;
|
||||
|
||||
export class CoWatch {
|
||||
// o: { api (path, body) -> {status, body}, wai, room, t0, seed (Uint8Array), principal (hex),
|
||||
|
|
@ -304,14 +308,20 @@ export class CoWatch {
|
|||
this.others = {}; // principal -> its last statement: at, position, error
|
||||
// `gone`: posts refused `not_present`, the room having taken the page for gone; `rejoins`:
|
||||
// its entries again; `resigned`: controls signed again after one; `dropped`: statements
|
||||
// and controls signed before one and not signed again.
|
||||
this.stats = { log: 0, controls: 0, applied: 0, refused: {}, statements: 0, posted: 0, post_refused: {}, seeks: 0, nudges: 0, verified_source: false, gone: 0, rejoins: 0, resigned: 0, dropped: 0, followed_at: null, errors: [] };
|
||||
// and controls signed before one and not signed again. `stalls`: the stretches in which
|
||||
// the player's loop did not run (`run`), the last STALLS_KEPT of them.
|
||||
this.stats = { log: 0, controls: 0, applied: 0, refused: {}, statements: 0, posted: 0, post_refused: {}, seeks: 0, nudges: 0, verified_source: false, gone: 0, rejoins: 0, resigned: 0, dropped: 0, followed_at: null, stalls: [], errors: [] };
|
||||
this.onChange = o.onChange || (() => {});
|
||||
}
|
||||
|
||||
/** The room clock now, on this page's estimate (§7.3). */
|
||||
now() {
|
||||
return Math.max(0, Math.round(Date.now() * 1000 + this.offset - this.t0));
|
||||
return this.roomTime(Date.now());
|
||||
}
|
||||
|
||||
/** The room clock at `ms` on this page's clock (`Date.now()`), on its estimate. */
|
||||
roomTime(ms) {
|
||||
return Math.max(0, Math.round(ms * 1000 + this.offset - this.t0));
|
||||
}
|
||||
|
||||
async estimateClock() {
|
||||
|
|
@ -547,12 +557,30 @@ export class CoWatch {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** Keep the media at the controlled position (§9) and state what it presents (§7.3). */
|
||||
/** Keep the media at the controlled position (§9) and state what it presents (§7.3).
|
||||
*
|
||||
* Each pass of the loop states, if half a slot has passed since its last statement, then
|
||||
* waits PASS_MS. Two statements are so at most half a slot and the gap between two passes
|
||||
* apart, and while no such gap is over half a slot every slot holds one: a slot is left empty
|
||||
* only if no pass began in its second half. The page records each gap between two passes
|
||||
* over half a slot (`stalls`): `{ from, to }`, and how long the wait and the signing in it
|
||||
* took (`timer_us`, `signing_us`), all on its estimate of the room clock. The rest of the gap
|
||||
* is the loop's own work. A wait or a signing that took long is the page's thread held, by
|
||||
* the page's own work or by its machine not running it; the record does not tell which. */
|
||||
async run(alive) {
|
||||
const per = (this.policy.period_ms * 1000) / STATES_PER_SLOT;
|
||||
let next = 0;
|
||||
let last = null; // the room clock at the previous pass
|
||||
let timer = 0; // how long its wait and its signing took, on the room clock (µs)
|
||||
let signing = 0;
|
||||
while (alive() && !this.stopped) {
|
||||
const x = this.now();
|
||||
if (last != null && x - last > per) {
|
||||
this.stats.stalls.push({ from: last, to: x, timer_us: timer, signing_us: signing });
|
||||
if (this.stats.stalls.length > STALLS_KEPT) this.stats.stalls.shift();
|
||||
}
|
||||
last = x;
|
||||
signing = 0;
|
||||
const st = this.timeline.stateAt(x);
|
||||
const duration = this.timeline.durationAt(x);
|
||||
const controlled = this.timeline.positionAt(x);
|
||||
|
|
@ -576,7 +604,9 @@ export class CoWatch {
|
|||
const pos = Math.round(this.media.currentTime * 1e6);
|
||||
const body = { room: this.room, player: this.principal, source: this.policy.source, at: x, position: pos, state, rate: Math.max(1, Math.round(this.media.playbackRate * 1e6)) };
|
||||
const entries = this.stats.rejoins;
|
||||
const s0 = this.now();
|
||||
const signed = await signPresentation(body, this.key.privateKey);
|
||||
signing = this.now() - s0;
|
||||
if (this.stats.rejoins !== entries) {
|
||||
// The page entered again while it signed (`flush`): `x` is an instant from before its
|
||||
// present join, which it states nothing of. It states anew at once.
|
||||
|
|
@ -589,16 +619,24 @@ export class CoWatch {
|
|||
}
|
||||
}
|
||||
this.onChange();
|
||||
await sleep(50);
|
||||
const w = this.now();
|
||||
await this.wait(PASS_MS);
|
||||
timer = this.now() - w;
|
||||
}
|
||||
}
|
||||
|
||||
/** The loop's wait between two passes. */
|
||||
wait(ms) {
|
||||
return sleep(ms);
|
||||
}
|
||||
|
||||
/** What a test reads, and what the page shows. */
|
||||
snapshot() {
|
||||
const last = this.skews.length ? this.skews[this.skews.length - 1] : null;
|
||||
const lastWindow = last ? this.skews.filter((s) => s.from === last.from) : [];
|
||||
const wall = Date.now();
|
||||
return {
|
||||
principal: this.principal, joined: this.joinT, at: this.at, offset_us: this.offset, room_now: this.now(), controls: this.controls.length, outbox: this.outbox.length,
|
||||
principal: this.principal, joined: this.joinT, at: this.at, offset_us: this.offset, room_now: this.roomTime(wall), wall_ms: wall, pass_ms: PASS_MS, controls: this.controls.length, outbox: this.outbox.length,
|
||||
overridden: this.controls.filter((c) => this.timeline.overridden.has(c.digest)).length,
|
||||
controlled: this.controlled ?? null, error_us: this.error ?? null,
|
||||
playing: !this.media.paused, rate: this.media.playbackRate, current_time: this.media.currentTime,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
// its correction (co-watch §9); each accepted room's controlled position recomputed from its
|
||||
// policies and controls (§5.4, §6), as the corpus states it at every change and at every
|
||||
// statement; its controls written byte for byte (§5.1); its presentation statements
|
||||
// written and signed byte for byte (§7.1), each at the rate the catch-up rule gives (§9); and
|
||||
// the player taken for gone by the room re-entering with its chain whole (§7.3, §10).
|
||||
// written and signed byte for byte (§7.1), each at the rate the catch-up rule gives (§9); the
|
||||
// player taken for gone by the room re-entering with its chain whole (§7.3, §10); and the
|
||||
// player stating in every slot while its loop runs, recording each stretch it did not (§7.3).
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
|
|
@ -135,7 +136,7 @@ const fakeWai = {
|
|||
// sign (503). `leaveAfterJoin`: the next joins whose presence a `leave` the room had asked
|
||||
// before them, signed after them, ends at once (a service that ordered them so).
|
||||
function watchService(lead_ms, late) {
|
||||
const s = { now: 0, present: false, joined: 0, lastT: 0, joins: 0, chain: [], controls: [], failJoins: 0, leaveAfterJoin: 0 };
|
||||
const s = { now: 0, present: false, joined: 0, lastT: 0, joins: 0, chain: [], controls: [], presentations: [], failJoins: 0, leaveAfterJoin: 0 };
|
||||
s.api = async (path, body) => {
|
||||
if (path === "/watch/challenge") return { status: 200, body: { challenge: "00".repeat(32) } };
|
||||
if (path === "/watch/join") {
|
||||
|
|
@ -167,6 +168,7 @@ function watchService(lead_ms, late) {
|
|||
s.chain.push(createHash("sha256").update(cw.unhex(h)).digest("hex"));
|
||||
s.lastT = ev.t;
|
||||
if (st && st.kind === "control") s.controls.push({ action: st.action, at: st.at, t: ev.t });
|
||||
if (st && st.kind === "presentation") s.presentations.push(st.at);
|
||||
}
|
||||
return { status: 200, body: { ok: true, carried: body.events.length } };
|
||||
};
|
||||
|
|
@ -328,3 +330,82 @@ test("a control pressed while the room had taken the page for gone is dropped wh
|
|||
assert.deepEqual(svc.controls.map((x) => x.action), [cw.ACTION.play, cw.ACTION.seek]);
|
||||
assert.ok(svc.controls[1].at > seekAt && svc.controls[1].at === svc.joined + lead * 1000 + 50_000);
|
||||
});
|
||||
|
||||
/** The player's loop run for `passes` passes on a room clock the test moves, so the test, not
|
||||
* the host, sets each pass's instant: by `step(i)` µs in the loop's `i`th wait between two
|
||||
* passes, and by `work(i)` µs in the work of its `i`th pass (in `onChange`, which the loop
|
||||
* calls in each pass, as work of its own or an await it does not measure would be); then
|
||||
* until the page has posted all it holds. */
|
||||
async function runFor(c, svc, passes, step, work = () => 0) {
|
||||
let i = 0;
|
||||
c.media = { currentTime: 0, paused: true, ended: false, readyState: 4, playbackRate: 1, play: () => Promise.resolve(), pause: () => {} };
|
||||
c.now = () => svc.now;
|
||||
c.onChange = () => { svc.now += work(i); };
|
||||
c.wait = async () => { svc.now += step(i++); await new Promise((r) => setTimeout(r, 0)); };
|
||||
await c.run(() => i < passes);
|
||||
await settled(c);
|
||||
}
|
||||
|
||||
/** The slots from the page's first statement to its last: each held by a statement, else
|
||||
* empty, and of those whose second half a stall of the page spans. */
|
||||
function slotsOf(ats, stalls, P) {
|
||||
const out = { held: 0, empty: [], stalled: [] };
|
||||
for (let s = Math.ceil(ats[0] / P) * P; s + P <= ats[ats.length - 1]; s += P) {
|
||||
if (ats.some((x) => x >= s && x < s + P)) out.held++;
|
||||
else (stalls.some((x) => x.from < s + P / 2 && x.to >= s + P) ? out.stalled : out.empty).push(s);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
test("while its loop runs, the player states in every slot and records no stall", async () => {
|
||||
const { svc, c, logs } = await entered();
|
||||
const P = c.policy.period_ms * 1000;
|
||||
await runFor(c, svc, 40, () => 50_000);
|
||||
assert.equal(c.stopped, null, logs.join("\n"));
|
||||
assert.deepEqual(c.stats.stalls, []);
|
||||
const s = slotsOf(svc.presentations, c.stats.stalls, P);
|
||||
assert.ok(s.held >= 1, JSON.stringify(s));
|
||||
assert.deepEqual([s.empty, s.stalled], [[], []]);
|
||||
for (let k = 1; k < svc.presentations.length; k++) assert.ok(svc.presentations[k] - svc.presentations[k - 1] <= P / 2 + 50_000);
|
||||
});
|
||||
|
||||
test("the player records the stretch its thread was held, and only then leaves a slot empty", async () => {
|
||||
const { svc, c, logs } = await entered();
|
||||
const P = c.policy.period_ms * 1000;
|
||||
// Its passes every 50 ms, but one: the page's thread held for three and a half slots while
|
||||
// the loop waited, so its wait took that long.
|
||||
const HELD = 15;
|
||||
await runFor(c, svc, 50, (i) => (i === HELD ? 3.5 * P : 50_000));
|
||||
assert.equal(c.stopped, null, logs.join("\n"));
|
||||
assert.equal(c.stats.stalls.length, 1, JSON.stringify(c.stats.stalls));
|
||||
const [st] = c.stats.stalls;
|
||||
assert.equal(st.to - st.from, 3.5 * P);
|
||||
// The whole stall is the wait: the loop's own work and its signing took no time on the clock.
|
||||
assert.deepEqual([st.timer_us, st.signing_us], [3.5 * P, 0]);
|
||||
// The slots the held thread left empty are those the stall spans; every other holds a
|
||||
// statement, and none is empty outside it.
|
||||
const s = slotsOf(svc.presentations, c.stats.stalls, P);
|
||||
assert.ok(s.stalled.length >= 2, JSON.stringify(s));
|
||||
assert.deepEqual(s.empty, []);
|
||||
for (const x of s.stalled) assert.ok(st.from < x + P / 2 && st.to >= x + P);
|
||||
// A pass more than half a slot after the one before is a stall: the loop states every half
|
||||
// slot, so only such a stretch can leave a slot empty.
|
||||
const { svc: svc2, c: c2 } = await entered();
|
||||
await runFor(c2, svc2, 30, (i) => (i === HELD ? P / 2 + 1 : i === HELD + 5 ? P / 2 : 50_000));
|
||||
assert.deepEqual(c2.stats.stalls.map((x) => x.to - x.from), [P / 2 + 1]);
|
||||
});
|
||||
|
||||
test("a stall in the loop's own work is recorded with a wait that does not cover it", async () => {
|
||||
const { svc, c, logs } = await entered();
|
||||
const P = c.policy.period_ms * 1000;
|
||||
// One pass's own work takes two slots (or an await the loop does not measure would): its
|
||||
// wait after it took 50 ms, and the stall is the rest.
|
||||
const HELD = 15;
|
||||
await runFor(c, svc, 50, () => 50_000, (i) => (i === HELD ? 2 * P : 0));
|
||||
assert.equal(c.stopped, null, logs.join("\n"));
|
||||
assert.equal(c.stats.stalls.length, 1, JSON.stringify(c.stats.stalls));
|
||||
const [st] = c.stats.stalls;
|
||||
assert.equal(st.to - st.from, 2 * P + 50_000);
|
||||
assert.deepEqual([st.timer_us, st.signing_us], [50_000, 0]);
|
||||
assert.equal(st.to - st.from - st.timer_us - st.signing_us, 2 * P);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,16 +6,30 @@
|
|||
# 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
|
||||
# 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:
|
||||
# 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:
|
||||
#
|
||||
# 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 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;
|
||||
# or, when files are given as arguments, those files (the self-test,
|
||||
|
|
@ -32,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
|
||||
|
||||
|
|
@ -45,19 +59,38 @@ 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
|
||||
|
||||
# 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
|
||||
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
|
||||
# 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.
|
||||
|
|
@ -66,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
|
||||
|
||||
|
|
@ -122,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)"
|
||||
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))
|
||||
|
|
@ -133,4 +166,4 @@ if [ $hits -gt 0 ]; then
|
|||
echo "for a functional identifier, mark the line with allow-name: <reason>."
|
||||
exit 1
|
||||
fi
|
||||
echo "public-name check: clean (${#files[@]} files)"
|
||||
echo "public-name check ($lists): clean (${#files[@]} files)"
|
||||
|
|
|
|||
107
scripts/check-wasmtime-pin-selftest.sh
Executable file
107
scripts/check-wasmtime-pin-selftest.sh
Executable file
|
|
@ -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 <old> <new>: 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 <pass|fail> <what> [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."
|
||||
144
scripts/check-wasmtime-pin.py
Executable file
144
scripts/check-wasmtime-pin.py
Executable file
|
|
@ -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_<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.
|
||||
|
||||
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))
|
||||
|
|
@ -9,8 +9,11 @@
|
|||
# - an opt-out with a reason is honoured, and one with an empty reason (the
|
||||
# comment form `<!-- allow-name: -->`, 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, 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.
|
||||
#
|
||||
# Exit status: 0 when every case gives its stated status, 1 otherwise.
|
||||
|
||||
|
|
@ -76,6 +79,24 @@ 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 <!-- allow-name: a repository URL -->\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"
|
||||
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"
|
||||
printf 'Its token rate is among the lowest of any codec.\n' > "$tmp/among.md"
|
||||
|
|
|
|||
141
wai/CHANGELOG.md
141
wai/CHANGELOG.md
|
|
@ -6,6 +6,144 @@ wire format may change and such changes are recorded here.
|
|||
|
||||
## Unreleased
|
||||
|
||||
### The native sink through the conformance runner, and a result that admits an implementation only for a run that passed
|
||||
|
||||
- **Admitting an implementation** ([extensions/conformance-results.md](extensions/conformance-results.md) §4.1). A
|
||||
relying party that admits an implementation on a `conform-result/1` record, such as a certification of a sink or a
|
||||
gate in a build, requires the record accepted (§4), its run passed (`not_conformant`), its suite one whose corpus it
|
||||
holds (`suite_unknown`), and its corpus digest that corpus's (`corpus_mismatch`). `wai verify --conformant [--root
|
||||
<wai-dir>]` applies the rules against the corpus of a checkout. A record of a run that did not pass is a genuine
|
||||
record, and `wai verify` without `--conformant` accepts it as before.
|
||||
- **A sink kind.** `wai conform sink --suite native-sink` gives a native sink every session of
|
||||
`live-conformance/native-sink/` with its answers withheld, and each configuration the corpus states a session
|
||||
refuses; a case passes when the sink's answers, one RFC 8785 text a line, have the SHA-256 of the answers the corpus
|
||||
states. `wai_native_sink_vectors answers [--c-abi] <case>` is the reference sink, through its module or through the C
|
||||
interface the platform integrations link.
|
||||
- **The native sink and the score decoder in CI** (`wai-breaking.yml`, the runner job): the native sink through its
|
||||
module and through its C interface, and the score decoder (`decoder --suite score`), each run conformant and each
|
||||
record admitted against the checkout's corpus. A planted sink that presents an object whose digest does not match
|
||||
fails its run, on that case; its record verifies, and admission refuses it at rule 2. `tests/cli_conform_sinks.rs`
|
||||
holds the same, and refuses a run against a copy of the corpus changed to suit the sink (`corpus_mismatch`) and a
|
||||
record of a suite the build does not run (`suite_unknown`). The browser sink has no `sink` suite:
|
||||
`wai_browser_sink_vectors verify` and `browser_sink_verify.py` hold its corpus, as before.
|
||||
|
||||
### Pin by default: an encoder pins the set a payload was coded against, and a verifier reports an envelope that does not
|
||||
|
||||
- **SPEC §3.1, *Pin by default*.** An encoder SHOULD pin the parameter set a payload was coded against wherever a
|
||||
capability that decodes it is registered as decoding against one, whatever the intent. Only `replicate` over a
|
||||
`NeuralIntegerExact` capability required a pin, so every other unpinned decode ran against whatever set the sink
|
||||
held, and a set other than the payload's gave plausible output, not an error. An envelope that pins nothing there
|
||||
still conforms in `1.x`, and a sink decodes it as before; a verifier reports it (`prior_unpinned`), informatively.
|
||||
- **The report.** `Wai::check_unpinned`, `WaiMulti::check_unpinned` and `codecs::pin_by_default` make it. `wai
|
||||
verify` now reads the envelopes (`*.wai`) under each path: it refuses one whose pin a verifier can tell is wrong
|
||||
without holding a set (SPEC §3.1, §7), and notes `prior_unpinned`, which refuses nothing. `wai inspect` notes it,
|
||||
and so does `wai wrap`, which pins no prior. `pin-conformance/unpinned.json` holds its vectors, reached by
|
||||
`wai_pin_vectors verify` and by `pin_verify.py`, which reads the classes from the published registry export.
|
||||
- **A record of a decode names the set it read.** A `reconstruct-session` decoder entry's `pin` (jwp-receipts
|
||||
§2.4.12) now names the set an unpinned decode read, by the digest a pin over those bytes names, with the tier
|
||||
`none`; it was `null`. The reference sink's base decode gives it (`staged::BaseDecode::set`, from
|
||||
`int_payload::IntParams::read_pin`), and the sessions `stage_measure` seals carry it. The page's recorder writes the
|
||||
same: `wai-web`'s `SessionHandle.decoderFor` takes the set the decode read (`StageBase.paramSet`) beside whether
|
||||
it verified a pin (`StageBase.pinned`), and `measure_verify.py` names the set as the text does. The fixture
|
||||
`staged-measure-conformance/fixtures/unpinned`, over the staged corpus's unpinned `wai.neural.int_hyper` base, holds
|
||||
both recorders and both verifiers to it; every other committed session and golden pins its bases, so none of them
|
||||
changes. A decoder entry sealed by a sink of the earlier text may carry `null` for an unpinned decode that read a
|
||||
set, and a verifier cannot tell it from a decode that read none (jwp-receipts §2.4.12). `claim-conformance/`'s
|
||||
vectors of an unpinned `wai.neural.int_hyper` decode name the set it read, and one more, appended, writes the base
|
||||
in the earlier text's form: a stage hash over either is refused (`session_stage_hash_over_an_unpinned_base`,
|
||||
`session_stage_hash_over_an_unpinned_base_earlier_text`).
|
||||
- **SPEC §3.1 rule 2** says that an unpinned envelope conforms unless §7 requires the pin (`replicate_unpinned`).
|
||||
|
||||
- **Breaking:** `api:rust:wai::staged::BaseDecode::set`: a new member of a public struct, the set the decode read; a
|
||||
struct literal or a pattern that lists every member adds it.
|
||||
- **Breaking:** `wire:corpus:wai/staged-measure-conformance/goldens.json`: it gains the `unpinned` fixture's
|
||||
evaluation, and its `dedup` record, taken over the objects of every fixture, now counts that fixture's too, so its
|
||||
totals move; no other fixture's evaluation changes. A verifier reads the goldens of the corpus it checks.
|
||||
|
||||
### A decode reports the path that ran, and no tier lies between the two
|
||||
|
||||
- **The reference dispatch reports the decode path** ([extensions/determinism-tiers.md](extensions/determinism-tiers.md)
|
||||
§5). `neural::decode_envelope`, `decode_envelope_strict`, `decode_multi`, `decode_multi_strict` and `decode_payload`
|
||||
return a `Dispatched`: the output, the capability the payload decoded through (its own or the fallback) and the path
|
||||
of it that ran, `DecodePath::Sole`, `Exact` or `Float`. `wai.image.jpegai`'s float decode through ONNX Runtime and its
|
||||
exact decoder now say which ran. The C ABI's `wai_decoded_t` carries the path (`WAI_DECODE_PATH_SOLE`, `_EXACT`,
|
||||
`_FLOAT`) and whether a `WAI1` envelope decoded through its `fallback`, so a native sink states them in its receipt.
|
||||
`neural::check_reconstruction_binding` reads the path and the binding's descriptor
|
||||
([extensions/reconstruction-binding.md](extensions/reconstruction-binding.md) §2): over a `decode-equivalence`
|
||||
capability or the exact decoder it reports whether the hash holds; over the float decode it reports the binding as not
|
||||
checked, and under a descriptor naming another reconstruction (another `decoderId`, format or size) as another
|
||||
reconstruction, never as failed. The notes that the dispatch did not report the path are gone from both texts.
|
||||
- **Where the decode path is tested.** `tests/jpegai_decode_path.rs` requires the result, the session claim and the
|
||||
binding check to state the path. Its cases with one path run on committed inputs in `wai-neural-gates.yml`; a job
|
||||
there fetches the pinned `jpegai/v1` bundle and runs the exact decoder with `WAI_REQUIRE_SOTA_VECTORS=1`. The float
|
||||
decode needs the JPEG-AI reference-software assets, which are not in the repository, so its test runs locally only,
|
||||
on a host that holds them, as `tests/jpegai_live.rs` does.
|
||||
- **No tier between the two** (determinism-tiers §3, normative). A pin's tier vocabulary stays closed. A decode of a
|
||||
`NeuralIntegerExact` capability in reduced precision or floating point is not a decode of it; a capability whose
|
||||
payload holds several frame types takes the class of its weakest; and a clip digest, a stage hash or a reconstruction
|
||||
binding over a sequence covers only frames each decoded at `decode-equivalence`, which a receipt checks per object.
|
||||
These rules govern decoded media; generation-equivalence carries its own digest of the frames generated.
|
||||
- **A session claim states its decode path** ([extensions/jwp-receipts.md](extensions/jwp-receipts.md) §2.4.12). A
|
||||
decoder entry's `variant` is `exact`, `float`, or `null` for a capability with one path; a `NeuralIntegerExact`
|
||||
capability's integer path may also be named by the lowering its registration names, as opus-dred §7.2 names
|
||||
`dred-int/1`. Those three are the exact paths. Rule 13 refuses a `NeuralIntegerExact` capability named with a path
|
||||
that is not exact (`session_variant_not_exact`); rule 14 refuses any path that is not exact under a pin of
|
||||
`decode-equivalence` (`session_tier_exceeds_variant`); and against the stage claims, a stage hash over a base an entry
|
||||
decoded at a weaker step, or over a base or layer an entry decoded by a path that is not exact, is refused
|
||||
(`session_stage_hash_not_exact`), whichever units that entry presented. `claim-conformance/` gains vectors for each,
|
||||
beside accepted ones for both `wai.image.jpegai` paths and for a registered lowering, appended after the earlier
|
||||
vectors, which a test pins by digest; `claim_verify.py` and `differential.py` read the same rules.
|
||||
- **determinism-tiers §1** records the five published routes to a reproducible stage, from the paper sections read, and
|
||||
a line that reaches no tier: float synchronised on a tested hardware class (arXiv 2608.00483). **§7.1** cites the two
|
||||
published records of float decode across devices (arXiv 2608.00483, 2606.28027) beside WAI's record. The research
|
||||
notes `syfai-input.md` §5 and `2026-09-global-sweep.md` (its row of arXiv 2608.00483, G7, G9 and F5) are corrected
|
||||
against the papers' bodies and the dispatch.
|
||||
- **A catalogue-prior record** ([research/catalogue-prior.md](research/catalogue-prior.md)): a linear basis learned on
|
||||
openly licensed films of one studio, measured against AV1, with its readings, its procedure, its decision rule
|
||||
and when that rule was fixed. The rule refutes a linear or spectral catalogue basis and exact recurrence across titles
|
||||
for a studio's catalogue; a learned prior is not measured. `tools/wai_catalogue_prior_verify.py` recomputes every
|
||||
BD-rate and verdict from the readings and checks every figure the page quotes, in the conformance workflow.
|
||||
|
||||
- **Breaking:** `api:rust:wai::claim_receipt::Reason::SessionVariantNotExact`,
|
||||
`api:rust:wai::claim_receipt::Reason::SessionTierExceedsVariant` and
|
||||
`api:rust:wai::claim_receipt::Reason::SessionStageHashNotExact`: new variants of an exhaustive enum, which an
|
||||
exhaustive match over `Reason` needs.
|
||||
- **Breaking:** `api:rust:wai::neural::decode_envelope`, `api:rust:wai::neural::decode_envelope_strict`,
|
||||
`api:rust:wai::neural::decode_multi`, `api:rust:wai::neural::decode_multi_strict` and
|
||||
`api:rust:wai::neural::decode_payload` return a `Dispatched` in place of a `Decoded`; the output is its `output`.
|
||||
The `neural` feature is outside the checked API, so the check does not report these.
|
||||
- **Breaking:** `api:c:wai_decoded_t`: two fields after `fps_x_1000`, `path` and `fallback`, so the struct is larger. A
|
||||
caller built against the old header is rebuilt against the new one.
|
||||
|
||||
### CI: a full runner disk fails a byte-equality leg as infrastructure, not as a divergence
|
||||
|
||||
- **CI:** each leg of the two cross-architecture byte-equality matrices (`wai-byte-equality.yml`, and
|
||||
`wai-conformance.yml`'s `byte-exact`) measures the free space under its workspace, the runner's temporary directory,
|
||||
TMPDIR and HOME before the checkout, and writes and syncs 32 MiB on each filesystem. Below 4 GiB, or on a write
|
||||
refused for space, an I/O error or a read-only filesystem, the leg fails in a step named for infrastructure, with an
|
||||
error that says no fixture was decoded. When a later step fails, the measurement and the write are made again, and
|
||||
the failure is named infrastructure if the disk filled meanwhile.
|
||||
|
||||
### 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
|
||||
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
|
||||
|
|
@ -672,7 +810,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
|
||||
|
|
|
|||
|
|
@ -105,17 +105,20 @@ the CHANGELOG state what WAI does, plainly:
|
|||
Three checks enforce this. `scripts/check-no-vanity-metrics.sh` (opt-out marker
|
||||
`allow-vanity: <reason>`) and `scripts/check-comparative-prose.sh` (opt-out
|
||||
`allow-phrase: <reason>`, 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: <reason>`. 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: <reason>`. 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
|
||||
|
|
|
|||
|
|
@ -107,8 +107,9 @@ wai verify claim.wai-conform.json # the signed result, checked
|
|||
|
||||
A break passes only when [`CHANGELOG.md`](CHANGELOG.md) names it: an entry this change adds that
|
||||
says **Breaking** and gives the finding's id in backticks. `wai conform --list` names every kind
|
||||
(verifier, decoder, packager, origin) and suite, with its adapters: a command line, or an
|
||||
`http://` URL.
|
||||
(verifier, decoder, packager, origin, sink) and suite, with its adapters: a command line, or an
|
||||
`http://` URL. `wai verify --conformant` admits a result only for a run that passed, against the
|
||||
corpus of the checkout it runs in.
|
||||
|
||||
With the `codecs` feature, `wai-rs/build.rs` finds libFLAC through pkg-config
|
||||
and, on macOS without pkg-config, in the Homebrew "flac" formula prefix
|
||||
|
|
@ -329,8 +330,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
|
||||
|
|
|
|||
37
wai/SPEC.md
37
wai/SPEC.md
|
|
@ -227,7 +227,9 @@ An envelope delivered over HTTP is one object of the proposed media type
|
|||
payload (§4), where that capability decodes against a parameter set. Absent, or
|
||||
`null`, the key is not written, and a decode through such a capability is
|
||||
unpinned: it runs against whatever parameter set the sink holds, and it cannot
|
||||
back `intent = replicate` over a `NeuralIntegerExact` capability (§7).
|
||||
back `intent = replicate` over a `NeuralIntegerExact` capability (§7). An encoder
|
||||
SHOULD write it wherever such a capability decodes the payload, whatever the intent
|
||||
(§3.1, *Pin by default*).
|
||||
- `security` — OPTIONAL. Absent, `null`, or `encryption: "none"` ⇒ the
|
||||
payload is plaintext (the default). Otherwise the payload is encrypted
|
||||
and MUST be recovered before dispatch; see §6. The capability, media,
|
||||
|
|
@ -424,6 +426,27 @@ decodes against a parameter set, and every such capability is registered, means
|
|||
nothing: an encoder MUST NOT write one, a verifier reports it
|
||||
(`prior_not_applicable`), and a sink ignores it.
|
||||
|
||||
**Pin by default.** A pin is OPTIONAL in `1.x`, and only `replicate` over a
|
||||
`NeuralIntegerExact` capability requires one (§7). Elsewhere an unpinned decode runs
|
||||
against whatever parameter set the sink holds, and a set other than the one the payload
|
||||
was coded against gives plausible output, not an error (§4). So:
|
||||
|
||||
1. An encoder SHOULD pin the parameter set a payload was coded against wherever a
|
||||
capability that decodes the payload — `model_requirement.capability`,
|
||||
`model_requirement.fallback`, or a `WAI2` entry's capability — is registered as
|
||||
decoding against one, whatever the envelope's `intent`. `wai.generate.spectral` is
|
||||
the exception above: its payload names its set.
|
||||
2. An envelope that pins nothing there conforms, unless §7 requires the pin
|
||||
(`replicate_unpinned`). A sink decodes it as before.
|
||||
3. A verifier reports it (`prior_unpinned`). The report is informative: it is not a
|
||||
finding that the envelope does not conform, and it is made whatever else the verifier
|
||||
finds. A capability this revision does not register is not reported, since this
|
||||
revision does not know what it decodes against.
|
||||
4. A sink that records a decode names the parameter set it read, pinned or not, by the
|
||||
digest a pin over those bytes names ([jwp-receipts](extensions/jwp-receipts.md)
|
||||
§2.4.12, a decoder entry's `pin`), so that a decode against another set is visible
|
||||
after the fact.
|
||||
|
||||
**Sinks of earlier revisions.** A sink that implements an earlier text of this
|
||||
specification may do one of two things with a pin (§8). It may reject the
|
||||
envelope as carrying a member it does not understand, which fails closed. Or it
|
||||
|
|
@ -2622,8 +2645,10 @@ it does for plaintext; in Lane A there is no output for it to cover.
|
|||
implementation, is the signed record `conform-result/1` of
|
||||
[conformance-results](extensions/conformance-results.md) §2. A verifier checks its signature
|
||||
and that its summary and its verdict are what its cases say
|
||||
([conformance-results](extensions/conformance-results.md) §4). `wai conform` writes such
|
||||
records and `wai verify` checks them.
|
||||
([conformance-results](extensions/conformance-results.md) §4). A party that admits an
|
||||
implementation on one, a sink among them, also requires that its run passed, against the
|
||||
corpus the party holds (§4.1 there). `wai conform` writes such records, and `wai verify`
|
||||
checks them (`--conformant`: admits them).
|
||||
|
||||
### Replicate-soundness — the integer-entropy contract (normative)
|
||||
|
||||
|
|
@ -2685,6 +2710,12 @@ registered at `decode-equivalence`. Two weaker properties MUST NOT back
|
|||
`replicate` resting on them promises something no sink can check from what it
|
||||
received.
|
||||
|
||||
No tier lies between these: a reconstruction kept within a bound of the exact one
|
||||
between points at which it is made exact again is no tier and backs nothing, a
|
||||
reduced-precision decode of a `NeuralIntegerExact` capability is not a decode of it,
|
||||
and a clip digest covers only frames each decoded at `decode-equivalence`
|
||||
([determinism-tiers](extensions/determinism-tiers.md) §3, *No tier between the two*).
|
||||
|
||||
The reference impl factors the halves of the contract into ORT-free, pure-i64
|
||||
modules a codec composes: `int_entropy` (the deterministic scale→bucket step
|
||||
that removes the float-σ desync), `int_synth` (integer synthesis), and the
|
||||
|
|
|
|||
|
|
@ -418,12 +418,13 @@
|
|||
},
|
||||
{
|
||||
"id": "receipt.verify",
|
||||
"summary": "Every receipt index (*.wai.receipts) under each path, its group receipts and claims checked offline, and every conformance result and fleet report bundle there",
|
||||
"summary": "Every receipt index (*.wai.receipts) under each path, its group receipts and claims checked offline, and every conformance result, fleet report bundle and envelope there",
|
||||
"cli": "wai verify",
|
||||
"aliases": [],
|
||||
"usage": [
|
||||
"wai verify <path>... the receipts (receipt indexes *.wai.receipts), conformance results (*.wai-conform.json)",
|
||||
" and fleet report bundles (*.wai-fleet.json) under each path, checked offline"
|
||||
"wai verify <path>... [--conformant [--root <wai-dir>]] the receipts (receipt indexes *.wai.receipts), conformance",
|
||||
" results (*.wai-conform.json), fleet report bundles (*.wai-fleet.json) and envelopes' pins (*.wai) under each path,",
|
||||
" checked offline; --conformant admits a result only for a run that passed against this checkout's corpus"
|
||||
],
|
||||
"placement": "local",
|
||||
"effect": "reads",
|
||||
|
|
@ -445,10 +446,25 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Files or directories holding receipt indexes, conformance results or fleet report bundles.",
|
||||
"description": "Files or directories holding receipt indexes, conformance results, fleet report bundles or envelopes.",
|
||||
"x-cli": {
|
||||
"positional": true
|
||||
}
|
||||
},
|
||||
"conformant": {
|
||||
"type": "boolean",
|
||||
"description": "Admit each conformance result only when its run passed, against the corpus of a checkout.",
|
||||
"x-cli": {
|
||||
"flag": "--conformant"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"type": "string",
|
||||
"format": "path",
|
||||
"description": "With --conformant, the wai/ directory of the checkout whose corpus a result names.",
|
||||
"x-cli": {
|
||||
"flag": "--root"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
|
@ -3466,7 +3482,7 @@
|
|||
"properties": {
|
||||
"kind": {
|
||||
"type": "string",
|
||||
"description": "The kind of implementation (wai conformance list): a decoder, a verifier, an origin, a packager.",
|
||||
"description": "The kind of implementation (wai conformance list): a decoder, a verifier, an origin, a packager, a sink.",
|
||||
"x-cli": {
|
||||
"positional": true
|
||||
}
|
||||
|
|
@ -3480,7 +3496,7 @@
|
|||
},
|
||||
"impl": {
|
||||
"type": "string",
|
||||
"description": "The implementation: a command with {in}/{out}/{corpus} placeholders, or a URL.",
|
||||
"description": "The implementation: a command with {case}/{in}/{out}/{corpus} placeholders, or a URL.",
|
||||
"x-cli": {
|
||||
"flag": "--impl"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,6 +349,55 @@ with neither is a hole; there is none.
|
|||
| SPEC §7.1, *Refinement components*, *Selection* (every `WaiMulti::check_component_roles` code: the refinement codes, `refines-on-non-refinement`, `refinement-only-as-component`, the companion codes, `component-id-malformed`, `duplicate-component-id`) | a sink offers a `WAI2`'s refinements only when every component conforms | rule 5, the whole envelope | `session_wai2_refinement_identity_missing`, `session_wai2_refines_base_missing`, `session_wai2_refines_base_companion`, `session_wai2_layer_base_not_admitted`, `session_wai2_refinement_prev_missing`, `session_wai2_refinement_before_selectable`, `session_roles_refinement_before_selectable_exploded`, `session_wai2_refines_on_a_rendition`, `session_roles_base_without_sha256`, `session_roles_layer_before_its_prev`, `session_roles_unbound_companion`, `session_roles_record_companion`, `session_roles_derivation_companion`, `session_roles_record_rendition`; accepted: `session_roles_companion_and_refinement_accept`, `session_roles_companion_by_id_and_refinement_accept` |
|
||||
| SPEC §7.1, *Companion components* | a companion that breaks rules 1–3 or its `companion_of_id`; an enhancement layer whose base is not unique | for the rendition picked, no bearing: a sink does not apply the companion and may still present the rendition, the output's digest holding whatever was applied; for the envelope's refinements, the row above | `session_roles_unbound_companion` |
|
||||
|
||||
### The decode path that ran
|
||||
|
||||
A decoder entry's `variant` states which path of its decoder ran: `exact`, `float`, or
|
||||
`null` for a capability with one path (jwp-receipts §2.4.12). A `NeuralIntegerExact`
|
||||
capability's integer path may also be named by the lowering its registration names, as
|
||||
opus-dred §7.2 names `dred-int/1`. `null`, `exact` and that lowering are the exact paths;
|
||||
any other term is not. Three rules close the door determinism-tiers §3 closes (*No tier
|
||||
between the two*), each with a refusal here. The vectors are appended after every earlier
|
||||
vector, which a test pins by digest:
|
||||
|
||||
1. Rule 13, `session_variant_not_exact`: a `NeuralIntegerExact` capability named with a
|
||||
path that is not exact. `session_variant_not_exact` names `wai.neural.int_hyper`
|
||||
with `float` under a pin of `decode-equivalence`, `session_variant_not_exact_unpinned`
|
||||
with an unregistered term and no pin verified, its entry naming the set it read with the
|
||||
tier `none`, and
|
||||
`session_variant_lowering_of_another_capability` with `dred-int/1`, the lowering of
|
||||
another capability. Accepted: `session_variant_exact_integer_accept`, the integer path
|
||||
stated as `exact`, and `session_variant_lowering_accept`, `wai.audio.opus.dred.int`'s
|
||||
stated as `dred-int/1`.
|
||||
2. Rule 14, `session_tier_exceeds_variant`: a path that is not exact stated under a pin of
|
||||
`decode-equivalence`, over a capability the registry does not classify, decoded by a
|
||||
module: `float` (`session_tier_exceeds_variant`) and `fp16`
|
||||
(`session_tier_exceeds_variant_unregistered_term`). `session_tier_exceeds_class_before_variant`
|
||||
gives `wai.image.jpegai`'s float path the same pin, which rule 6 refuses first.
|
||||
Accepted: `session_variant_float_accept` and `session_variant_exact_accept`,
|
||||
`wai.image.jpegai`'s two paths stated as they ran.
|
||||
|
||||
determinism-tiers §3 rule 2 (a capability whose payload holds several frame types
|
||||
takes the class of its weakest) is enforced over the registry class, by rule 6
|
||||
(`session_tier_exceeds_class`): the class a capability is registered at is that of
|
||||
its weakest frame type, so a pin above it is refused whatever path an entry states.
|
||||
`session_tier_exceeds_class_inter_frame` gives `wai.neural.dcvc_rt`, an inter-frame
|
||||
capability of the class `neural-float`, a pin of `decode-equivalence`. Rule 14 is the
|
||||
per-path complement: it refuses the same pin over the path that ran, where the registry
|
||||
gives no class.
|
||||
3. Against the stage claims, `session_stage_hash_not_exact`: the model session's stage
|
||||
claim stated at `decode-equivalence`, with its stage hash, a clip digest, while its base
|
||||
was also decoded through its fallback, a capability held to a tolerance, for unit 2,
|
||||
inside the set (`session_stage_hash_not_exact`); while the fallback entry names the
|
||||
base and presents only unit 5, outside the set, which is refused all the same, the
|
||||
check being per object (`session_stage_hash_not_exact_outside_the_set`); while one of
|
||||
its layers is stated as decoded on a `float` path
|
||||
(`session_stage_hash_over_a_float_layer`); or while its base is stated as decoded with
|
||||
no pin verified, its entry naming the set it read with the tier `none`
|
||||
(`session_stage_hash_over_an_unpinned_base`), or with `pin` and `tier` `null`, as a sink
|
||||
of an earlier text of jwp-receipts writes an unpinned decode
|
||||
(`session_stage_hash_over_an_unpinned_base_earlier_text`). Accepted:
|
||||
`session_stage_hash_exact_accept`.
|
||||
|
||||
## Steps 1 and 2: types, and the order a body is read in
|
||||
|
||||
§3.1 step 2 checks exactly the type a member table gives, member by member in the
|
||||
|
|
|
|||
|
|
@ -671,6 +671,17 @@ def replicate(cap):
|
|||
return c["replicate"] if c else None
|
||||
|
||||
|
||||
def exact_path(d):
|
||||
"""§2.4.12, a decoder entry's `variant`: whether it states an exact path — null, `exact`,
|
||||
or, for a capability of the replicate class neural-int-exact, the lowering its
|
||||
registration's `ops` member names (such as `dred-int/1`). Any other term is not exact."""
|
||||
v = d["variant"]
|
||||
if v is None or v == "exact":
|
||||
return True
|
||||
c = REGISTRY.get(d["capability"])
|
||||
return c is not None and c["replicate"] == "neural-int-exact" and (c.get("ops") or {}).get("lowering") == v
|
||||
|
||||
|
||||
def class_tier(cap):
|
||||
"""SPEC §3.1 *Tier*: the tier a capability's class gives — decode-equivalence for an
|
||||
integer-exact neural decode, entropy-consistency for an entropy-exact one, none for
|
||||
|
|
@ -731,6 +742,14 @@ def session_rules(b):
|
|||
if e is not None:
|
||||
need(e["joules_micro"] != 0, 4, "energy_zero_figure")
|
||||
acquisition(e)
|
||||
# Rule 13: a NeuralIntegerExact capability is decoded by its integer path only, which
|
||||
# an entry states as an exact path (determinism-tiers §3, rule 1).
|
||||
need(not any(replicate(d["capability"]) == "neural-int-exact" and not exact_path(d) for d in ds), 4,
|
||||
"session_variant_not_exact")
|
||||
# Rule 14: frames a path that is not exact decoded are not decode-equivalent, whatever
|
||||
# the pin declares (rule 2 there).
|
||||
need(not any(not exact_path(d) and d["tier"] == "decode-equivalence" for d in ds), 4,
|
||||
"session_tier_exceeds_variant")
|
||||
|
||||
|
||||
# ------------------------------------------- §2.4.12 step 6: what a session is checked against
|
||||
|
|
@ -1276,7 +1295,8 @@ def check_session_envelopes(c, envs, objs, ps):
|
|||
def check_session_stage_claims(c, wires, ps, objs):
|
||||
"""§2.4.12 against the stage claims. A sink names one for each epoch of a stage set,
|
||||
and bases over the same units of the title each have their own: each claim's
|
||||
identity and objects first, then with the outputs each claim's units and its count
|
||||
identity and objects first, then a stage hash against the decoder entries that
|
||||
decoded its base and layers, then with the outputs each claim's units and its count
|
||||
from below, and each unit of a set from above, by the objects listed as applied or
|
||||
partial by the claims whose sets hold it."""
|
||||
if not isinstance(wires, list) or not all(isinstance(w, str) for w in wires):
|
||||
|
|
@ -1304,6 +1324,22 @@ def check_session_stage_claims(c, wires, ps, objs):
|
|||
for h in [s["subject"]] + [o["object"] for o in s["body"]["objects"]]:
|
||||
at.setdefault(h, set()).update(named)
|
||||
need(all(d["first_unit"] in at[d["object"]] for d in b["decoders"] if d["object"] in at), 6, "session_first_unit_mismatch")
|
||||
# A stage hash is a clip digest over the set's units (staged-delivery §5): every entry
|
||||
# naming the set's base decodes it at decode-equivalence by that section's base step,
|
||||
# and no entry naming the base or a layer the claim lists ran a path that is not exact
|
||||
# (determinism-tiers §3, rule 3), whichever units the entry presented.
|
||||
def base_exact(d):
|
||||
r = replicate(d["capability"])
|
||||
return r == "standard-defined" or (r == "neural-int-exact" and d["tier"] == "decode-equivalence")
|
||||
|
||||
for s, _ in claims:
|
||||
if s["body"]["stage_hash"] is None:
|
||||
continue
|
||||
layers = {o["object"] for o in s["body"]["objects"]}
|
||||
for d in b["decoders"]:
|
||||
base = d["object"] == s["subject"]
|
||||
need(not (base and not base_exact(d)) and not ((base or d["object"] in layers) and not exact_path(d)),
|
||||
6, "session_stage_hash_not_exact")
|
||||
if ps is None:
|
||||
return
|
||||
fin = finals(ps)
|
||||
|
|
|
|||
|
|
@ -591,10 +591,11 @@ SESSION_ENERGY = [
|
|||
]
|
||||
|
||||
|
||||
def session_stage_claim(rng, n_set, at_stage_1, signer, subject, late=0):
|
||||
def session_stage_claim(rng, n_set, at_stage_1, signer, subject, late=0, tier="none"):
|
||||
"""A stage claim (§2.4.10) that passes its own rules: `n_set` units, `at_stage_1` of
|
||||
them at stage 1 through one applied first layer, the rest at stage 0; and, with
|
||||
`late`, a first layer over the next `late` units that arrived late."""
|
||||
`late`, a first layer over the next `late` units that arrived late. At `tier`
|
||||
`decode-equivalence` it carries a stage hash, a clip digest of its units."""
|
||||
ubs = [n_set - at_stage_1, at_stage_1] if at_stage_1 else [n_set]
|
||||
objects = [] if not at_stage_1 else [{
|
||||
"layer": 1, "object": CV.blake3(b"differential: a stage layer").hex(), "units": {"from": 0, "count": at_stage_1},
|
||||
|
|
@ -604,8 +605,9 @@ def session_stage_claim(rng, n_set, at_stage_1, signer, subject, late=0):
|
|||
"units": {"from": at_stage_1, "count": late}, "bytes": 650, "status": "late", "evidence": "none",
|
||||
"reason": None})
|
||||
body = {"vocabulary": CV.STAGE_VOCABULARY, "op_requested": None, "op_committed": None, "op_delivered": None,
|
||||
"presented": {"wai-stage:layers": 0 if ubs[0] else 1}, "units_by_stage": ubs, "tier": "none",
|
||||
"stage_hash": None, "objects": objects, "conflicts": 0, "deadline_misses": 0, "bytes_envelopes": 5700}
|
||||
"presented": {"wai-stage:layers": 0 if ubs[0] else 1}, "units_by_stage": ubs, "tier": tier,
|
||||
"stage_hash": CV.blake3(b"differential: a clip digest").hex() if tier == "decode-equivalence" else None,
|
||||
"objects": objects, "conflicts": 0, "deadline_misses": 0, "bytes_envelopes": 5700}
|
||||
c = {"kind": "claim", "class": "stage", "subject_type": "object", "subject": subject, "seq": 0,
|
||||
"issued_at": "2026-09-27T12:00:00Z", "body": body, "signer_id": "did:example:sink", "parent_receipt_hash": None}
|
||||
return CV.jcs(sign(c, signer))
|
||||
|
|
@ -721,12 +723,23 @@ def sessions(ins, rng, n):
|
|||
reached = sum(1 for u, p in fin.items() if f <= u < f + n_set and p["layers"] > 1)
|
||||
at1 = min(n_set, max(0, reached + rng.choice([0, 0, 1, -1])))
|
||||
signer = VIEWER if rng.random() < 0.9 else RELAY
|
||||
base_hash = CV.blake3(b"differential: a base %d" % i).hex()
|
||||
w = session_stage_claim(rng, n_set, at1, signer, base_hash)
|
||||
# Its base: one no entry names, or the entry object, which entries name and decode
|
||||
# at whatever step their capability, pin and path give; at decode-equivalence the
|
||||
# claim carries a stage hash (§2.4.12 step 6, against the stage claims, item 5).
|
||||
base_hash = CV.blake3(b"differential: a base %d" % i).hex() if rng.random() < 0.7 else entry
|
||||
stage_tier = rng.choice(["none", "none", "decode-equivalence"])
|
||||
w = session_stage_claim(rng, n_set, at1, signer, base_hash, tier=stage_tier)
|
||||
if rng.random() < 0.05:
|
||||
w = w.replace('"tier":"none"', '"tier":"nonе"')
|
||||
stage_wires.append(w)
|
||||
stage_sets.append({"first_unit": f, "claim": CV.link_hash(json.loads(w)) if rng.random() < 0.95 else "00" * 32})
|
||||
# An entry naming the set's base has the unit the session names the set at, as an
|
||||
# honest sink states it, so a stage hash is checked against the step and the path
|
||||
# of each entry that decoded the base (item 5) and not refused before it (item 4).
|
||||
if base_hash == entry and rng.random() < 0.8:
|
||||
for d in decoders:
|
||||
if d["object"] == entry:
|
||||
d["first_unit"] = f
|
||||
# The set's base and its layer object among the objects received, or not.
|
||||
if rng.random() < 0.85:
|
||||
objects.append(obj(base_hash, counted=4000))
|
||||
|
|
@ -752,7 +765,7 @@ def sessions(ins, rng, n):
|
|||
# One or two defects, in the body or in a context.
|
||||
inert = None
|
||||
for _ in range(rng.choice([0, 1, 1, 2])):
|
||||
k = rng.randrange(31)
|
||||
k = rng.randrange(33)
|
||||
ds = body["decoders"]
|
||||
if k == 0:
|
||||
body["primary_units"] += rng.choice([1, -1]) if body["primary_units"] else 1
|
||||
|
|
@ -833,6 +846,23 @@ def sessions(ins, rng, n):
|
|||
o = rng.choice(objects)
|
||||
o["counted"] = rng.choice([None, 0, (o["counted"] or 0) + 1, max(0, (o["counted"] or 0) - 1)])
|
||||
restate(body, objects)
|
||||
elif k == 31 and ds:
|
||||
# The decode path an entry states (§2.4.12 rules 13 and 14): exact, float, a
|
||||
# term this revision does not register, a lowering a registration names
|
||||
# (whm1/1 is the model session's integer capabilities' own), or none.
|
||||
rng.choice(ds)["variant"] = rng.choice([None, "exact", "float", "fp16", "whm1/1", "dred-int/1"])
|
||||
elif k == 32 and ds:
|
||||
# A float path under a pin of either tier, naming the set read with no pin
|
||||
# verified, or naming no set, through a capability the registry classifies or
|
||||
# one it does not.
|
||||
d = rng.choice(ds)
|
||||
d["variant"] = rng.choice(["float", "exact", "fp16", "dred-int/1"])
|
||||
d["pin"], d["tier"] = rng.choice([(CV.blake3(b"differential: a pin").hex(), "decode-equivalence"),
|
||||
(CV.blake3(b"differential: a pin").hex(), "entropy-consistency"),
|
||||
(CV.blake3(b"differential: a pin").hex(), "none"), (None, None)])
|
||||
if rng.random() < 0.6:
|
||||
d["capability"] = rng.choice(["org.example.picture", "org.example.picture", "wai.image.jpegai",
|
||||
"wai.audio.opus.dred.int"])
|
||||
if rng.random() < 0.5:
|
||||
ctx["redecoded"] = [{"index": j, "out_digest": rng.choice([p["out_digest"], CV.blake3(b"differential: redecoded").hex()])}
|
||||
for j, p in enumerate(ps) if rng.random() < 0.3]
|
||||
|
|
@ -897,8 +927,20 @@ def session_stage_bounds(ins, rng, n):
|
|||
if rng.random() < 0.15:
|
||||
# The base's entry, or its layer's, at another first unit than the set's.
|
||||
rng.choice(decoders)["first_unit"] += 1
|
||||
# At decode-equivalence the claim states a stage hash (item 5), which holds while the
|
||||
# base's entry decodes it at decode-equivalence and each entry runs an exact path;
|
||||
# now and then the base's entry is unpinned (naming the set it read with the tier
|
||||
# none, or with pin and tier null as a sink of an earlier text writes it), or an
|
||||
# entry names another path.
|
||||
tier = rng.choice(["none", "none", "decode-equivalence"])
|
||||
if tier == "decode-equivalence" and rng.random() < 0.5:
|
||||
d = rng.choice(decoders)
|
||||
if d is decoders[0] and rng.random() < 0.5:
|
||||
d["pin"], d["tier"] = rng.choice([(CV.blake3(b"differential: a pin").hex(), "none"), (None, None)])
|
||||
else:
|
||||
d["variant"] = rng.choice(["float", "fp16", "exact", "whm1/1"])
|
||||
w = session_stage_claim(rng, n_set, rng.choice([at1, at1, max(0, at1 - 1), min(n_set, at1 + 1)]), VIEWER, base_hash,
|
||||
late=late if late and at1 + late <= n_set else 0)
|
||||
late=late if late and at1 + late <= n_set else 0, tier=tier)
|
||||
objects = []
|
||||
for h, cnt in [(base_hash, 4000), (CV.blake3(b"differential: a stage layer").hex(), 700),
|
||||
(CV.blake3(b"differential: a late stage layer").hex(), 650)]:
|
||||
|
|
@ -1578,6 +1620,25 @@ def session_caps(ins):
|
|||
{"outputs": copy.deepcopy(ps)})
|
||||
|
||||
|
||||
def session_decode_paths(ins):
|
||||
"""§2.4.12 rules 6, 13 and 14 where nothing else decides: one picture through one entry,
|
||||
for every capability below (classified at each tier, or not at all), decoded by a
|
||||
builtin or a module, on a path named by every term below (exact, float, a lowering a
|
||||
registration names, one it names for another capability, one no revision registers, or
|
||||
none), under a pin of either tier, naming the set read with no pin verified (the tier
|
||||
`none`), or naming no set."""
|
||||
base = ins.receipt("session_variant_float_accept")
|
||||
pin = CV.blake3(b"differential: a pin").hex()
|
||||
for cap in ["org.example.picture", "wai.image.jpegai", "wai.neural.int_hyper", "wai.audio.opus.dred.int", "wai.neural.dcvc_rt"]:
|
||||
for variant in [None, "exact", "float", "fp16", "dred-int/1", "whm1/1", "jpegai-int/1"]:
|
||||
for tier in [None, "none", "entropy-consistency", "decode-equivalence"]:
|
||||
for decoder, status in [("builtin:[email protected]", None), ("module:sha256:" + "5e" * 32, "pinned")]:
|
||||
d = dict(base["body"]["decoders"][0], capability=cap, variant=variant, pin=pin if tier else None, tier=tier,
|
||||
decoder=decoder, module_status=status)
|
||||
body = dict(base["body"], decoders=[d])
|
||||
ins.add(f"session_decode_path_{cap}_{variant}_{tier}_{status}", CV.jcs(sign(dict(base, body=body), VIEWER)))
|
||||
|
||||
|
||||
def figures(ins, rng, n):
|
||||
oer_t = ins.by_name["energy_matches_oer_accept"]["context"]["oer"]["receipt"]
|
||||
claim_t = ins.receipt("energy_matches_oer_accept")
|
||||
|
|
@ -2091,6 +2152,7 @@ def main(argv):
|
|||
stages(ins, random.Random(f"stages {a.seed}"), a.random) # its own stream, as classes()
|
||||
sessions(ins, random.Random(f"sessions {a.seed}"), a.random) # its own stream, as classes()
|
||||
session_caps(ins)
|
||||
session_decode_paths(ins)
|
||||
session_stage_bounds(ins, random.Random(f"session bounds {a.seed}"), a.random) # its own stream, as classes()
|
||||
session_stage_epochs(ins, random.Random(f"session epochs {a.seed}"), a.random) # its own stream, as classes()
|
||||
session_held(ins, random.Random(f"session held {a.seed}"), a.random) # its own stream, as classes()
|
||||
|
|
|
|||
|
|
@ -24651,6 +24651,218 @@
|
|||
"step": 2,
|
||||
"reason": "unknown_member"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_float_accept",
|
||||
"about": "a picture decoded by wai.image.jpegai's float path, stated as variant float: the path that ran, beside the capability, which is registered at entropy-consistency",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.image.jpegai\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":\"float\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"343061bf444e80e3420dde80299ee8f4da440f269933abf85506e024eb5b8414839841cf942306399bacecc1d0fe5376df1519098378d0a400f6a667e3accc06\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "accept",
|
||||
"link_hash": "3bac449e6ddf59c72fb2c6f7922fe12952c597db69c2c36c18a0ce2f8dbca86a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_exact_accept",
|
||||
"about": "the same picture decoded by wai.image.jpegai's exact decoder, stated as variant exact, under a pin of entropy-consistency",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.image.jpegai\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"entropy-consistency\",\"variant\":\"exact\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"f128bb5e54c0122b393661b58ffccd2cceec289e2cbc090a55e122988669bfe46839ec5a4199f3a30e44a2aed125a6cccceb966786cc33da9d25ce54c1b31d0f\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "accept",
|
||||
"link_hash": "5e5d01814308da6c65efe318051861c3c6abd36662afb51ef09cdee7c59715c4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_exact_integer_accept",
|
||||
"about": "a NeuralIntegerExact capability whose entry states its integer path as variant exact, under a pin of decode-equivalence",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.neural.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"exact\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"40639afc5ae903add05e95b3c30a0bb5f55affb86849d3f10c86b532f59f195f70464a73c747679f2ebf4456e341a5741b7a218d3160272bd7877ba937ac8f0e\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "accept",
|
||||
"link_hash": "068b95433825974508a3cf0adfece27e78314207865a14130504fe02e4023d4a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_not_exact",
|
||||
"about": "a NeuralIntegerExact capability named with variant float: a decode of it in floating point is not a decode of it (determinism-tiers 3, rule 1)",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.neural.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"float\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"675f06e109118c31bf9987cc257c4c244de7bf1318ade4284ddf108b77294503b9c1b4568dffd0ee448dd8a27b1b9304735d4592edfc09a68dd1fba40e8a4d0f\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_variant_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_not_exact_unpinned",
|
||||
"about": "the same with no pin verified, its entry naming the set it read with the tier none, and a path named by a term this revision does not register: only null, exact or the capability's registered lowering names the integer path",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.neural.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"none\",\"variant\":\"fp16\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0f53b6de5fe4a90a9fe8a74292c13e6f7e01267ac334512ff47f9f3cdf77aa31d25cbac82963626f9cdb92b3a0fa7bd01cd3a4b16f87f8fef643294ac1625400\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_variant_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_lowering_accept",
|
||||
"about": "a NeuralIntegerExact capability whose entry names its integer path by the lowering its registration names, as opus-dred 7.2's present entry names wai.audio.opus.dred.int's, dred-int/1, under a pin of decode-equivalence",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.audio.opus.dred.int\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"dred-int/1\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"audio\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"f82c5ef640689d9c5c7aae666a97fc474c9a38aebab818be479c2aa5c6b616fe7567868a831a7a63e25ad0ae93667543f8fce63d53dadd2d5602fbc09e266802\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "accept",
|
||||
"link_hash": "60fb99e3ef042894f22fdc683aa5220c5d0b9d65fe6034b5cc7729c5d6494980"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_variant_lowering_of_another_capability",
|
||||
"about": "wai.neural.int_hyper named with dred-int/1, the lowering another capability registers: not its integer path",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.neural.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"dred-int/1\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"da9ecd87b76cd33eb1d89d0e5bcf214feacdd8b1ac20d91966ff0569da0643a17fc0364ba32b5f19b296bb1a75e246792d1a27af2b82bd4adc8160d74e42710e\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_variant_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_tier_exceeds_variant",
|
||||
"about": "a capability the registry does not classify, decoded by a module on its float path and stated under a pin of decode-equivalence: a float path's frames are not decode-equivalent, whatever the pin declares (determinism-tiers 3, rule 2)",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"org.example.picture\",\"decoder\":\"module:sha256:5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e\",\"first_unit\":0,\"leaves\":1,\"module_status\":\"pinned\",\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"float\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"85e1d82d42125df5862e49a1ef44da98097a07a723c3fa63b39f1a03081fd9b700d52c2a02d583716ebc3b71d72a4825224eec0187bb324d23ef89504c5e9504\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_tier_exceeds_variant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_tier_exceeds_variant_unregistered_term",
|
||||
"about": "the same capability on a path named by a term this revision does not register, fp16, under a pin of decode-equivalence: for a capability the registry does not classify, only null or exact names an exact path",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"org.example.picture\",\"decoder\":\"module:sha256:5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e\",\"first_unit\":0,\"leaves\":1,\"module_status\":\"pinned\",\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"fp16\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"2a8f2a28351478bdba85f5407d17d57723f6b90446b0748cf857320d423b94440e647b168acc99179c01e57a45d61d066e36d40ebd12f09934fa4ca9d01f4b00\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_tier_exceeds_variant"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_tier_exceeds_class_before_variant",
|
||||
"about": "wai.image.jpegai's float path under a pin of decode-equivalence: the class rule, rule 6, refuses it first",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.image.jpegai\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":\"float\"}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"image\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"e4dc0e43885391ef620f38606f58847b2392c385652a269ab2bdefa04684851665958c49826cff1accfd57c052d005aaf0a72c8b42826c664e49b5e9c5a8fc01\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_tier_exceeds_class"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_tier_exceeds_class_inter_frame",
|
||||
"about": "an inter-frame capability, wai.neural.dcvc_rt, under a pin of decode-equivalence: its registry class, neural-float, is the class of its weakest frame type, and rule 6 refuses a tier above it whatever path the entry states (determinism-tiers 3, rule 2)",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":null,\"classical_source\":null,\"counted_discarded\":0,\"counted_fallback\":0,\"counted_primary\":0,\"deadline_us\":null,\"decoders\":[{\"capability\":\"wai.neural.dcvc_rt\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":null}],\"energy\":null,\"fallback_units\":0,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":1,\"media\":\"video\",\"missed\":0,\"missed_units\":[],\"output_root\":\"9bbb2c453e90eab8c5e9ca19a9b9f355103fa5d112ba9b50f23f277b3849e7e7\",\"primary_units\":1,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[],\"switches\":[],\"switches_total\":0,\"transferred\":367,\"uncounted\":1,\"units\":1},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"1e5a701567e8d1b628bacf8aebba898025ee7ef83ae427959836c8f7a81281d02899706a89f8f4a7d9ba17cdabd4f155ee46eca616c3400c85c9aacaf943c405\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"666d5383cd03ed68010ca0f5d2749addc0145bc4b14f864eae26d876b7f247c9\",\"subject_type\":\"object\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "session_tier_exceeds_class"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_stage_hash_exact_accept",
|
||||
"about": "the model session's stage set stated at decode-equivalence, with its stage hash: its base decoded only through a pinned integer capability, its layers through a residual refinement, every path exact",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":700,\"classical_source\":\"wai2-table\",\"counted_discarded\":1904,\"counted_fallback\":0,\"counted_primary\":5935,\"deadline_us\":40000,\"decoders\":[{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":4,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":4,\"leaves\":1,\"module_status\":null,\"object\":\"f18f33c4eebadf2caf00fb22a38cf394e19d31a62ebe68f3043c31c84ecbcf44\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":0,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":5,\"leaves\":1,\"module_status\":null,\"object\":\"13b04249eca2b1a9061dba8ca2846169d3f5fa3548d9e138a0cf2cb553798cf4\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null}],\"energy\":null,\"fallback_units\":1,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":10,\"media\":\"video\",\"missed\":1,\"missed_units\":[{\"late_us\":1200,\"layers\":1,\"unit\":5}],\"output_root\":\"b62a8425b7fe3a085a14f2f2156ea22ae3b4349884d799b5afe66dccc7d4dd7f\",\"primary_units\":5,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[{\"claim\":\"edc1de4f22af2c57dca1a362e302b7fe6309bd2e7840ab05c957002bf527b3f0\",\"first_unit\":0}],\"switches\":[{\"from\":\"primary\",\"reason\":\"set-incomplete\",\"to\":\"fallback\",\"unit\":5}],\"switches_total\":1,\"transferred\":9995,\"uncounted\":2,\"units\":6},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c89804c9e944f3d69dbeadebc29214349ce7ddd74f09bf8b1a579217e3ec4bc0b91d8e5a78b8281d6907cbfb1e3443d4a8de050b9a3869797873b1ad6ed4b04\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}",
|
||||
"context": {
|
||||
"stage_claims": [
|
||||
"{\"body\":{\"bytes_envelopes\":10607,\"conflicts\":0,\"deadline_misses\":2,\"objects\":[{\"bytes\":900,\"evidence\":\"none\",\"layer\":1,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":901,\"evidence\":\"none\",\"layer\":1,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":1}},{\"bytes\":902,\"evidence\":\"none\",\"layer\":1,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":2}},{\"bytes\":903,\"evidence\":\"none\",\"layer\":1,\"object\":\"792a12990c5207ca83a86b7420815f1f2d34918553a0130a4f9570a11354f2ca\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":3}},{\"bytes\":1000,\"evidence\":\"none\",\"layer\":2,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":1001,\"evidence\":\"none\",\"layer\":2,\"object\":\"1c6eae0ed41ed7fc1b2f00b46ea1de98f21a6d6cbf62bf6b22cf76a664a49376\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":1}}],\"op_committed\":null,\"op_delivered\":null,\"op_requested\":null,\"presented\":{\"wai-stage:layers\":0},\"stage_hash\":\"082bc00210e6d32f18f39ee723d863fd7b99f206a415ac2f44a8687793b2e068\",\"tier\":\"decode-equivalence\",\"units_by_stage\":[1,2,1],\"vocabulary\":\"urn:wai:staged-delivery:1\"},\"class\":\"stage\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c29c7a2e9cfdb393273506b5acc9672b225192b28b9114f3b814d19c91bf4f994a74f2eecc077abbe6840c84f98f09ebbd2bd4f08d84592cca9a944918f590d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "accept",
|
||||
"link_hash": "08f4298f5975d5594ed62f58bac25726e7ca0bccdf9d4e7b83d25656e13746a7"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_stage_hash_not_exact",
|
||||
"about": "the same stage claim, while the base's unit 2, inside the set, was decoded through the base's fallback, wai.video.av1, a capability held to a tolerance, by an entry of its own naming the base: a clip digest over a sequence one of whose frames was decoded at a weaker tier (determinism-tiers 3, rule 3)",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":700,\"classical_source\":\"wai2-table\",\"counted_discarded\":1904,\"counted_fallback\":0,\"counted_primary\":5935,\"deadline_us\":40000,\"decoders\":[{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":3,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":4,\"leaves\":1,\"module_status\":null,\"object\":\"f18f33c4eebadf2caf00fb22a38cf394e19d31a62ebe68f3043c31c84ecbcf44\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":0,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":5,\"leaves\":1,\"module_status\":null,\"object\":\"13b04249eca2b1a9061dba8ca2846169d3f5fa3548d9e138a0cf2cb553798cf4\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null}],\"energy\":null,\"fallback_units\":1,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":10,\"media\":\"video\",\"missed\":1,\"missed_units\":[{\"late_us\":1200,\"layers\":1,\"unit\":5}],\"output_root\":\"3063938fe3dec97a08fde52f174c51f16dfd9511dfe82534e325fdf1fd60431b\",\"primary_units\":5,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[{\"claim\":\"edc1de4f22af2c57dca1a362e302b7fe6309bd2e7840ab05c957002bf527b3f0\",\"first_unit\":0}],\"switches\":[{\"from\":\"primary\",\"reason\":\"deadline-threshold\",\"to\":\"fallback\",\"unit\":2},{\"from\":\"fallback\",\"reason\":\"recovered\",\"to\":\"primary\",\"unit\":3},{\"from\":\"primary\",\"reason\":\"set-incomplete\",\"to\":\"fallback\",\"unit\":5}],\"switches_total\":3,\"transferred\":9995,\"uncounted\":2,\"units\":6},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"b0d40437162946ed862996e4a3e8223bf42daddce5ba8d0e8c5517ce223d5feaeb4f55b87553126acba2f277e89cc8d102e1a795afd95c398eab40c5907ac200\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}",
|
||||
"context": {
|
||||
"stage_claims": [
|
||||
"{\"body\":{\"bytes_envelopes\":10607,\"conflicts\":0,\"deadline_misses\":2,\"objects\":[{\"bytes\":900,\"evidence\":\"none\",\"layer\":1,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":901,\"evidence\":\"none\",\"layer\":1,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":1}},{\"bytes\":902,\"evidence\":\"none\",\"layer\":1,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":2}},{\"bytes\":903,\"evidence\":\"none\",\"layer\":1,\"object\":\"792a12990c5207ca83a86b7420815f1f2d34918553a0130a4f9570a11354f2ca\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":3}},{\"bytes\":1000,\"evidence\":\"none\",\"layer\":2,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":1001,\"evidence\":\"none\",\"layer\":2,\"object\":\"1c6eae0ed41ed7fc1b2f00b46ea1de98f21a6d6cbf62bf6b22cf76a664a49376\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":1}}],\"op_committed\":null,\"op_delivered\":null,\"op_requested\":null,\"presented\":{\"wai-stage:layers\":0},\"stage_hash\":\"082bc00210e6d32f18f39ee723d863fd7b99f206a415ac2f44a8687793b2e068\",\"tier\":\"decode-equivalence\",\"units_by_stage\":[1,2,1],\"vocabulary\":\"urn:wai:staged-delivery:1\"},\"class\":\"stage\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c29c7a2e9cfdb393273506b5acc9672b225192b28b9114f3b814d19c91bf4f994a74f2eecc077abbe6840c84f98f09ebbd2bd4f08d84592cca9a944918f590d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 6,
|
||||
"reason": "session_stage_hash_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_stage_hash_not_exact_outside_the_set",
|
||||
"about": "the same stage claim, while the fallback entry, wai.video.av1, names the base and presents only unit 5, outside the set: refused all the same, since the check is per object, whichever units an entry presented (determinism-tiers 3, rule 3)",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":700,\"classical_source\":\"wai2-table\",\"counted_discarded\":1904,\"counted_fallback\":0,\"counted_primary\":5935,\"deadline_us\":40000,\"decoders\":[{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":4,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":4,\"leaves\":1,\"module_status\":null,\"object\":\"f18f33c4eebadf2caf00fb22a38cf394e19d31a62ebe68f3043c31c84ecbcf44\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":0,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null}],\"energy\":null,\"fallback_units\":1,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":10,\"media\":\"video\",\"missed\":1,\"missed_units\":[{\"late_us\":1200,\"layers\":1,\"unit\":5}],\"output_root\":\"b62a8425b7fe3a085a14f2f2156ea22ae3b4349884d799b5afe66dccc7d4dd7f\",\"primary_units\":5,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[{\"claim\":\"edc1de4f22af2c57dca1a362e302b7fe6309bd2e7840ab05c957002bf527b3f0\",\"first_unit\":0}],\"switches\":[{\"from\":\"primary\",\"reason\":\"set-incomplete\",\"to\":\"fallback\",\"unit\":5}],\"switches_total\":1,\"transferred\":9995,\"uncounted\":2,\"units\":6},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"607fcbc40cc5a84709125d99f6b8fe2e3e840c218d6f8b70319c09cb92e40e4a1626d0c3d93a1e0d6efbfed6594ef0fbcdc1a0f762cebd6e98d1e4966b77d80b\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}",
|
||||
"context": {
|
||||
"stage_claims": [
|
||||
"{\"body\":{\"bytes_envelopes\":10607,\"conflicts\":0,\"deadline_misses\":2,\"objects\":[{\"bytes\":900,\"evidence\":\"none\",\"layer\":1,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":901,\"evidence\":\"none\",\"layer\":1,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":1}},{\"bytes\":902,\"evidence\":\"none\",\"layer\":1,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":2}},{\"bytes\":903,\"evidence\":\"none\",\"layer\":1,\"object\":\"792a12990c5207ca83a86b7420815f1f2d34918553a0130a4f9570a11354f2ca\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":3}},{\"bytes\":1000,\"evidence\":\"none\",\"layer\":2,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":1001,\"evidence\":\"none\",\"layer\":2,\"object\":\"1c6eae0ed41ed7fc1b2f00b46ea1de98f21a6d6cbf62bf6b22cf76a664a49376\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":1}}],\"op_committed\":null,\"op_delivered\":null,\"op_requested\":null,\"presented\":{\"wai-stage:layers\":0},\"stage_hash\":\"082bc00210e6d32f18f39ee723d863fd7b99f206a415ac2f44a8687793b2e068\",\"tier\":\"decode-equivalence\",\"units_by_stage\":[1,2,1],\"vocabulary\":\"urn:wai:staged-delivery:1\"},\"class\":\"stage\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c29c7a2e9cfdb393273506b5acc9672b225192b28b9114f3b814d19c91bf4f994a74f2eecc077abbe6840c84f98f09ebbd2bd4f08d84592cca9a944918f590d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 6,
|
||||
"reason": "session_stage_hash_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_stage_hash_over_a_float_layer",
|
||||
"about": "the same stage claim, while one of the set's first layers is stated as decoded on a float path",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":700,\"classical_source\":\"wai2-table\",\"counted_discarded\":1904,\"counted_fallback\":0,\"counted_primary\":5935,\"deadline_us\":40000,\"decoders\":[{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":4,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":4,\"leaves\":1,\"module_status\":null,\"object\":\"f18f33c4eebadf2caf00fb22a38cf394e19d31a62ebe68f3043c31c84ecbcf44\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":0,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":5,\"leaves\":1,\"module_status\":null,\"object\":\"13b04249eca2b1a9061dba8ca2846169d3f5fa3548d9e138a0cf2cb553798cf4\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":\"float\"},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null}],\"energy\":null,\"fallback_units\":1,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":10,\"media\":\"video\",\"missed\":1,\"missed_units\":[{\"late_us\":1200,\"layers\":1,\"unit\":5}],\"output_root\":\"b62a8425b7fe3a085a14f2f2156ea22ae3b4349884d799b5afe66dccc7d4dd7f\",\"primary_units\":5,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[{\"claim\":\"edc1de4f22af2c57dca1a362e302b7fe6309bd2e7840ab05c957002bf527b3f0\",\"first_unit\":0}],\"switches\":[{\"from\":\"primary\",\"reason\":\"set-incomplete\",\"to\":\"fallback\",\"unit\":5}],\"switches_total\":1,\"transferred\":9995,\"uncounted\":2,\"units\":6},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"162060aa0f64815777fe03a59eb676528c1cfb6390b530d885bb65d1d97d29f7dc0729ba5534b265e0c5cc0b5e45fcb2ff53548d106c153078ad93ba9260740f\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}",
|
||||
"context": {
|
||||
"stage_claims": [
|
||||
"{\"body\":{\"bytes_envelopes\":10607,\"conflicts\":0,\"deadline_misses\":2,\"objects\":[{\"bytes\":900,\"evidence\":\"none\",\"layer\":1,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":901,\"evidence\":\"none\",\"layer\":1,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":1}},{\"bytes\":902,\"evidence\":\"none\",\"layer\":1,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":2}},{\"bytes\":903,\"evidence\":\"none\",\"layer\":1,\"object\":\"792a12990c5207ca83a86b7420815f1f2d34918553a0130a4f9570a11354f2ca\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":3}},{\"bytes\":1000,\"evidence\":\"none\",\"layer\":2,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":1001,\"evidence\":\"none\",\"layer\":2,\"object\":\"1c6eae0ed41ed7fc1b2f00b46ea1de98f21a6d6cbf62bf6b22cf76a664a49376\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":1}}],\"op_committed\":null,\"op_delivered\":null,\"op_requested\":null,\"presented\":{\"wai-stage:layers\":0},\"stage_hash\":\"082bc00210e6d32f18f39ee723d863fd7b99f206a415ac2f44a8687793b2e068\",\"tier\":\"decode-equivalence\",\"units_by_stage\":[1,2,1],\"vocabulary\":\"urn:wai:staged-delivery:1\"},\"class\":\"stage\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c29c7a2e9cfdb393273506b5acc9672b225192b28b9114f3b814d19c91bf4f994a74f2eecc077abbe6840c84f98f09ebbd2bd4f08d84592cca9a944918f590d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 6,
|
||||
"reason": "session_stage_hash_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_stage_hash_over_an_unpinned_base",
|
||||
"about": "the same stage claim, while the base is stated as decoded with no pin verified, its entry naming the set it read with the tier none: its NeuralIntegerExact capability holds decode-equivalence only under a pin that declares it (staged-delivery 5)",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":700,\"classical_source\":\"wai2-table\",\"counted_discarded\":1904,\"counted_fallback\":0,\"counted_primary\":5935,\"deadline_us\":40000,\"decoders\":[{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":4,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":null,\"tier\":\"none\",\"variant\":null},{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":4,\"leaves\":1,\"module_status\":null,\"object\":\"f18f33c4eebadf2caf00fb22a38cf394e19d31a62ebe68f3043c31c84ecbcf44\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":0,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":5,\"leaves\":1,\"module_status\":null,\"object\":\"13b04249eca2b1a9061dba8ca2846169d3f5fa3548d9e138a0cf2cb553798cf4\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null}],\"energy\":null,\"fallback_units\":1,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":10,\"media\":\"video\",\"missed\":1,\"missed_units\":[{\"late_us\":1200,\"layers\":1,\"unit\":5}],\"output_root\":\"b62a8425b7fe3a085a14f2f2156ea22ae3b4349884d799b5afe66dccc7d4dd7f\",\"primary_units\":5,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[{\"claim\":\"edc1de4f22af2c57dca1a362e302b7fe6309bd2e7840ab05c957002bf527b3f0\",\"first_unit\":0}],\"switches\":[{\"from\":\"primary\",\"reason\":\"set-incomplete\",\"to\":\"fallback\",\"unit\":5}],\"switches_total\":1,\"transferred\":9995,\"uncounted\":2,\"units\":6},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"c98f973df33707192c3a999563f40bf5c907124879ccc3e6291ff3ebb06602e8839f81f1ac22a24b5e885c0e999c1aa64ef78f3bb5585a5cbf5ce1cfb7696b0d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}",
|
||||
"context": {
|
||||
"stage_claims": [
|
||||
"{\"body\":{\"bytes_envelopes\":10607,\"conflicts\":0,\"deadline_misses\":2,\"objects\":[{\"bytes\":900,\"evidence\":\"none\",\"layer\":1,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":901,\"evidence\":\"none\",\"layer\":1,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":1}},{\"bytes\":902,\"evidence\":\"none\",\"layer\":1,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":2}},{\"bytes\":903,\"evidence\":\"none\",\"layer\":1,\"object\":\"792a12990c5207ca83a86b7420815f1f2d34918553a0130a4f9570a11354f2ca\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":3}},{\"bytes\":1000,\"evidence\":\"none\",\"layer\":2,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":1001,\"evidence\":\"none\",\"layer\":2,\"object\":\"1c6eae0ed41ed7fc1b2f00b46ea1de98f21a6d6cbf62bf6b22cf76a664a49376\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":1}}],\"op_committed\":null,\"op_delivered\":null,\"op_requested\":null,\"presented\":{\"wai-stage:layers\":0},\"stage_hash\":\"082bc00210e6d32f18f39ee723d863fd7b99f206a415ac2f44a8687793b2e068\",\"tier\":\"decode-equivalence\",\"units_by_stage\":[1,2,1],\"vocabulary\":\"urn:wai:staged-delivery:1\"},\"class\":\"stage\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c29c7a2e9cfdb393273506b5acc9672b225192b28b9114f3b814d19c91bf4f994a74f2eecc077abbe6840c84f98f09ebbd2bd4f08d84592cca9a944918f590d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 6,
|
||||
"reason": "session_stage_hash_not_exact"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "session_stage_hash_over_an_unpinned_base_earlier_text",
|
||||
"about": "the same, with the base's entry as a sink of an earlier text of this extension writes an unpinned decode, pin and tier null, which a verifier cannot tell from a decode that read no set: refused all the same, since no pin declares decode-equivalence",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"classical_declared\":700,\"classical_source\":\"wai2-table\",\"counted_discarded\":1904,\"counted_fallback\":0,\"counted_primary\":5935,\"deadline_us\":40000,\"decoders\":[{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":4,\"module_status\":null,\"object\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_hyper\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":4,\"leaves\":1,\"module_status\":null,\"object\":\"f18f33c4eebadf2caf00fb22a38cf394e19d31a62ebe68f3043c31c84ecbcf44\",\"path\":\"primary\",\"pin\":\"7b4371854df0aa1645ac6525b373387c0617b873d5a57748932ee0f386ea164b\",\"rendition\":0,\"tier\":\"decode-equivalence\",\"variant\":null},{\"capability\":\"wai.video.av1\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":5,\"leaves\":1,\"module_status\":null,\"object\":\"13b04249eca2b1a9061dba8ca2846169d3f5fa3548d9e138a0cf2cb553798cf4\",\"path\":\"fallback\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null},{\"capability\":\"wai.video.int_refine\",\"decoder\":\"builtin:[email protected]\",\"first_unit\":0,\"leaves\":1,\"module_status\":null,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"path\":\"primary\",\"pin\":null,\"rendition\":null,\"tier\":null,\"variant\":null}],\"energy\":null,\"fallback_units\":1,\"framing\":\"registered\",\"host\":{\"build\":{\"profile\":\"release\",\"target\":\"aarch64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.95.0\"},\"profile\":\"native-ffi\",\"target\":\"aarch64-unknown-linux-gnu\",\"threads\":4},\"leaf_count\":10,\"media\":\"video\",\"missed\":1,\"missed_units\":[{\"late_us\":1200,\"layers\":1,\"unit\":5}],\"output_root\":\"b62a8425b7fe3a085a14f2f2156ea22ae3b4349884d799b5afe66dccc7d4dd7f\",\"primary_units\":5,\"receipt_bytes\":640,\"session_nonce\":\"57c5462dfdabd1b7bdd204d34ba33649c057eb535349f29f45eb8f8c7d4a7e7f\",\"stage_sets\":[{\"claim\":\"edc1de4f22af2c57dca1a362e302b7fe6309bd2e7840ab05c957002bf527b3f0\",\"first_unit\":0}],\"switches\":[{\"from\":\"primary\",\"reason\":\"set-incomplete\",\"to\":\"fallback\",\"unit\":5}],\"switches_total\":1,\"transferred\":9995,\"uncounted\":2,\"units\":6},\"class\":\"reconstruct-session\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"d8f9aa9a0222d8a800e880d7fe6f6295ca39d842f727244b6d6458cbaafe4e3e7acb0d26015d64c6cd843cb2ca27aa0eaa775d172487a68ed32def05fa0edc0a\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}",
|
||||
"context": {
|
||||
"stage_claims": [
|
||||
"{\"body\":{\"bytes_envelopes\":10607,\"conflicts\":0,\"deadline_misses\":2,\"objects\":[{\"bytes\":900,\"evidence\":\"none\",\"layer\":1,\"object\":\"47a2c23727d2eb9d2beef0931c3e9b2675408d372660420cce427dc27fd21fe1\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":901,\"evidence\":\"none\",\"layer\":1,\"object\":\"6b74be11e64461c7d972fc5cb6ba15e98427e9d6e60622b0a753222138de28cf\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":1}},{\"bytes\":902,\"evidence\":\"none\",\"layer\":1,\"object\":\"5e7a38ca5c04442cbe00b040d58e4494f5aa36fb81e8bbe0d304fd2d1ce722b2\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":2}},{\"bytes\":903,\"evidence\":\"none\",\"layer\":1,\"object\":\"792a12990c5207ca83a86b7420815f1f2d34918553a0130a4f9570a11354f2ca\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":3}},{\"bytes\":1000,\"evidence\":\"none\",\"layer\":2,\"object\":\"be5055af8e32359265852c512667b10ab6a07455e5ab06603e278763f6633a85\",\"reason\":null,\"status\":\"applied\",\"units\":{\"count\":1,\"from\":0}},{\"bytes\":1001,\"evidence\":\"none\",\"layer\":2,\"object\":\"1c6eae0ed41ed7fc1b2f00b46ea1de98f21a6d6cbf62bf6b22cf76a664a49376\",\"reason\":null,\"status\":\"late\",\"units\":{\"count\":1,\"from\":1}}],\"op_committed\":null,\"op_delivered\":null,\"op_requested\":null,\"presented\":{\"wai-stage:layers\":0},\"stage_hash\":\"082bc00210e6d32f18f39ee723d863fd7b99f206a415ac2f44a8687793b2e068\",\"tier\":\"decode-equivalence\",\"units_by_stage\":[1,2,1],\"vocabulary\":\"urn:wai:staged-delivery:1\"},\"class\":\"stage\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"0c29c7a2e9cfdb393273506b5acc9672b225192b28b9114f3b814d19c91bf4f994a74f2eecc077abbe6840c84f98f09ebbd2bd4f08d84592cca9a944918f590d\",\"signer_id\":\"did:example:sink\",\"signer_pubkey\":\"d759793bbc13a2819a827c76adb6fba8a49aee007f49f2d0992d99b825ad2c48\",\"subject\":\"d09526cec3961c21e36cc08f0b393323eb82dcc4203cb08df57fee36784b1113\",\"subject_type\":\"object\"}"
|
||||
]
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 6,
|
||||
"reason": "session_stage_hash_not_exact"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@
|
|||
// play from Ada's seek at the instant the room carried, within the bound, until Ada pauses
|
||||
// them at one position again. At the end the room's log shows what happened, and each page's
|
||||
// own counts agree with it: one `leave`, Ben's, in the hold; each page stating from its join,
|
||||
// before it had its relay session, and in every slot while it was present (co-watch §7.3);
|
||||
// Ada never taken for gone, Ben once; and no page stopped.
|
||||
// before it had its relay session, and in every slot while it was present (co-watch §7.3),
|
||||
// but for a slot in whose second half the machine ran nothing of the page, neither its
|
||||
// statement loop nor a worker of its own; Ada never taken for gone, Ben once; and no page
|
||||
// stopped.
|
||||
//
|
||||
// node wai/examples/watch-together/test/watch-together.test.mjs
|
||||
import { check, create, dev, dump, finish, frame, launch, open, sleep, STEP, tmpdir, until } from "../../../quickstart/test/lib.mjs";
|
||||
|
|
@ -29,6 +31,18 @@ try {
|
|||
pages.push(p);
|
||||
const f = await frame(p, "/room/");
|
||||
await until(`${name}: the room page ready`, STEP, () => f.evaluate(() => window.roomTest && window.roomTest.state && window.roomTest.state.phase === "idle"), 200);
|
||||
// The page's heartbeat: a worker of its own that does nothing but tick, every 20 ms, and
|
||||
// records each stretch over 100 ms between two ticks, on the page's clock (Date.now(), ms).
|
||||
// It runs on a thread of its own, in the page's process: the page's work on its own thread
|
||||
// does not hold it, and the machine running nothing of the page does. Its ticking while the
|
||||
// page's thread did not run does not say why: the page's own work, or the machine running
|
||||
// this thread and not that one.
|
||||
await f.evaluate(() => {
|
||||
const src = "let last = Date.now(); setInterval(() => { const t = Date.now(); if (t - last > 100) postMessage([last, t]); last = t; }, 20);";
|
||||
const h = { gaps: [], worker: new Worker(URL.createObjectURL(new Blob([src], { type: "text/javascript" }))) };
|
||||
h.worker.onmessage = (e) => h.gaps.push({ from: e.data[0], to: e.data[1] });
|
||||
window.heartbeat = h;
|
||||
});
|
||||
await f.click("#watch");
|
||||
const s = await until(`${name}: in the watch room`, STEP, async () => {
|
||||
const x = await f.evaluate(() => window.roomTest.snapshot());
|
||||
|
|
@ -108,21 +122,71 @@ try {
|
|||
check("Ada entered once, Ben twice: again after his leave", ja.length === 1 && jb.length === 2 && leaves.length === 1 && jb[1].entry > leaves[0].entry, `${ja.length} and ${jb.length} joins`);
|
||||
// Every slot wholly within a stretch of a page's presence holds a statement of it (co-watch
|
||||
// §7.3), from its join to a slot before the last statement the log holds, and for Ben's
|
||||
// first stretch to a slot before his hold began.
|
||||
// first stretch to a slot before his hold began. The page's loop states every half slot while
|
||||
// it runs (cowatch.js `run`), so a slot it left empty is one in whose second half no pass of
|
||||
// it began: a stall of the loop the page records (`stalls`), with how long its wait and its
|
||||
// signing took. Such a slot is counted apart only if the machine ran nothing of the page in
|
||||
// that half slot: the page's heartbeat (its worker, which the page's own work does not hold)
|
||||
// has no tick there either, and the stall is its wait and its signing, but for at most one
|
||||
// pass of the loop's own work. A slot left empty while the heartbeat ticked fails, whether
|
||||
// the page's own work held its thread or the machine ran the worker and not the page's thread:
|
||||
// the check does not tell the two apart, and counts neither apart. So does one left empty by
|
||||
// an await of the loop's own, or with no stall at all.
|
||||
const stated = (p) => L.entries.filter((e) => e.principal === p && e.kind === "presentation").map((e) => e.at);
|
||||
const last = Math.max(...stated(A.principal), ...stated(B.principal)) - P;
|
||||
// Each page's heartbeat, and its clock (ms) as the room clock (µs): room = ms * 1000 + k.
|
||||
const beat = await Promise.all([A, B].map((x) => x.f.evaluate(() => {
|
||||
const c = window.roomTest.snapshot().cowatch;
|
||||
return { gaps: window.heartbeat.gaps, k: c.room_now - c.wall_ms * 1000 };
|
||||
})));
|
||||
let slots = 0;
|
||||
let widest = 0;
|
||||
const empty = (name, p, from, to) => {
|
||||
const ats = stated(p).filter((x) => x >= from && x < to);
|
||||
ats.forEach((x, i) => { if (i) widest = Math.max(widest, x - ats[i - 1]); });
|
||||
const apart = [];
|
||||
// Why the empty slot from `s` of page `i` fails the check; null if the machine ran nothing
|
||||
// of the page in its second half.
|
||||
const unexcused = (i, s) => {
|
||||
const c = end[i];
|
||||
const pass = c.pass_ms * 1000;
|
||||
const st = c.stalls.find((x) => x.from < s + P / 2 && x.to >= s + P);
|
||||
if (!st) return "no stall of its loop";
|
||||
const why = [];
|
||||
const [w0, w1] = [s + P / 2, s + P].map((r) => (r - beat[i].k) / 1000);
|
||||
if (!beat[i].gaps.some((g) => g.from <= w0 + c.pass_ms && g.to >= w1 - c.pass_ms)) why.push("its heartbeat ticking");
|
||||
const own = st.to - st.from - st.timer_us - st.signing_us;
|
||||
if (own > pass) why.push(`${ms(own)} of it neither its wait nor its signing`);
|
||||
return why.length ? `its loop's stall ${st.from}..${st.to}, ${why.join(", ")}` : null;
|
||||
};
|
||||
const empty = (i, from, to) => {
|
||||
const name = [A, B][i].name;
|
||||
const ats = stated(end[i].principal).filter((x) => x >= from && x < to);
|
||||
ats.forEach((x, k) => { if (k) widest = Math.max(widest, x - ats[k - 1]); });
|
||||
const out = [];
|
||||
for (let s = Math.ceil(from / P) * P; s + P <= to; s += P, slots++) if (!ats.some((x) => x >= s && x < s + P)) out.push(`${name} ${s}`);
|
||||
for (let s = Math.ceil(from / P) * P; s + P <= to; s += P, slots++) {
|
||||
if (ats.some((x) => x >= s && x < s + P)) continue;
|
||||
const why = unexcused(i, s);
|
||||
if (why) out.push(`${name} ${s}: ${why}`);
|
||||
else apart.push(`${name} ${s}`);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
const gaps = ja.length === 1 && jb.length === 2 ? [...empty("ada", A.principal, ja[0].t, last), ...empty("ben", B.principal, jb[0].t, hold.began - P), ...empty("ben", B.principal, jb[1].t, last)] : ["no stretches to check"];
|
||||
check("each page stated what it presented in every slot while it was present (co-watch §7.3), but in Ben's hold", gaps.length === 0,
|
||||
gaps.length ? `no statement in the slot from ${gaps.slice(0, 6).join(", ")}` : `${slots} slots; at most ${ms(widest)} between two statements of a page`);
|
||||
const gaps = ja.length === 1 && jb.length === 2 ? [...empty(0, ja[0].t, last), ...empty(1, jb[0].t, hold.began - P), ...empty(1, jb[1].t, last)] : ["no stretches to check"];
|
||||
// What each page recorded, printed whether the check holds or not: when its audio context was
|
||||
// made and how long that held the thread, the stalls of its loop with their wait and signing,
|
||||
// and the stretches its heartbeat did not tick, all on the room clock. They say whether the
|
||||
// page's thread or the whole page was held, not what held it.
|
||||
const seen = end.map((c, i) => {
|
||||
const o = c.audio_open;
|
||||
const joined = [ja, jb][i][0].t;
|
||||
const st = c.stalls.slice(0, 8).map((x) => `${x.from}..${x.to} (wait ${ms(x.timer_us)}, signing ${ms(x.signing_us)})`);
|
||||
const longest = [...beat[i].gaps].sort((x, y) => (y.to - y.from) - (x.to - x.from)).slice(0, 8).sort((x, y) => x.from - y.from);
|
||||
const hb = longest.map((g) => `${g.from * 1000 + beat[i].k}..${g.to * 1000 + beat[i].k}`);
|
||||
return `${[A, B][i].name}: audio context made at ${o ? `${o.at}, ${o.at < joined ? "before" : "after"} its first join at ${joined}, in ${ms(o.took_us)}` : "none"}; `
|
||||
+ `${c.stalls.length} stall(s) of its loop${st.length ? `: ${st.join(", ")}` : ""}; `
|
||||
+ `its heartbeat silent over 100 ms ${beat[i].gaps.length} time(s)${hb.length ? `, the longest ${hb.join(", ")}` : ""}`;
|
||||
}).join("; ");
|
||||
check("each page stated what it presented in every slot while it was present (co-watch §7.3), but in Ben's hold and where the machine ran nothing of the page", gaps.length === 0,
|
||||
(gaps.length ? `no statement in the slot from ${gaps.slice(0, 6).join("; ")}` : `${slots} slots; at most ${ms(widest)} between two statements of a page`)
|
||||
+ (apart.length ? `; ${apart.length} empty with the machine running nothing of the page: ${apart.slice(0, 6).join(", ")}` : "") + `. ${seen}`);
|
||||
check("each page stated from its join, before it had its relay session and followed the log",
|
||||
[a, b].every((c) => c.followed_at != null && stated(c.principal).length && stated(c.principal)[0] < c.followed_at), [a, b].map((c, i) => `${[A, B][i].name}: first at ${stated(c.principal)[0]}, following from ${c.followed_at}`).join("; "));
|
||||
check("Ada's page was never taken for gone, Ben's once, entering again once with his play signed again; the room refused no other event of either, and no page stopped",
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
## 1. Scope
|
||||
|
||||
In scope: the record (§2), its signing input and signature (§3), what a verifier checks and in
|
||||
what order (§4), and the codes it refuses with (§5).
|
||||
what order (§4), what a relying party that admits an implementation on a record checks besides
|
||||
(§4.1), and the codes a verifier refuses a record with (§5).
|
||||
|
||||
Out of scope: how a run reaches an implementation (a command line, a URL), which corpora and
|
||||
suites exist, and what makes an implementation conformant beyond the run a record states. A
|
||||
|
|
@ -34,7 +35,7 @@ Every other number, and every member not listed, makes the record malformed.
|
|||
| member | type | value |
|
||||
|---|---|---|
|
||||
| `wai` | string | `conform-result/1`, the record's kind and version |
|
||||
| `kind` | string | the kind of implementation run: `verifier`, `decoder`, `packager`, `origin`, or another kind a later revision names |
|
||||
| `kind` | string | the kind of implementation run: `verifier`, `decoder`, `packager`, `origin`, `sink`, or another kind a later revision names |
|
||||
| `suite` | string | the suite that ran |
|
||||
| `standard` | string | the version of WAI whose corpus ran (`v0.4.0`) |
|
||||
| `toolchain` | object | the program that ran the corpus: `name` and `version`, each a string |
|
||||
|
|
@ -103,9 +104,38 @@ writes its outcome as `accept conformant=true`, `accept conformant=false`, or
|
|||
`reject step=<n> reason=<code>`. A record that says its run was not conformant is a genuine
|
||||
record of that run, and is accepted as one.
|
||||
|
||||
### 4.1 Admitting an implementation
|
||||
|
||||
Acceptance shows that a record is intact and consistent; it admits nothing (§8). A relying
|
||||
party that admits an implementation on a record, such as a certification of a sink or a gate in
|
||||
a build, holds the corpus the record names, and applies these rules in this order. It refuses
|
||||
the implementation at the first rule that fails, with the code that rule names:
|
||||
|
||||
1. The record is accepted (§4). Otherwise the code of the step of §4 that refused it.
|
||||
2. Its `conformant` is `true` (`not_conformant`). A record of a run that did not pass is
|
||||
genuine, and admits nothing.
|
||||
3. Its `kind` and `suite` name a suite whose corpus the relying party holds and reads
|
||||
(`suite_unknown`).
|
||||
4. Its `corpus.digest` is the corpus digest (§2.1) of that suite's files as the relying party
|
||||
holds them (`corpus_mismatch`). A run against another corpus, an earlier revision's or one
|
||||
changed to suit an implementation, admits nothing.
|
||||
|
||||
These rules read the record and the corpus the relying party holds, and nothing that depends on
|
||||
the runner, so two relying parties that hold the same corpus admit the same records. They do not
|
||||
say who ran the run: a relying party that requires a known runner also requires `key` to be
|
||||
`supplied` and confirms `signer_pubkey` (§6). `wai verify --conformant` applies them, against
|
||||
the corpus of a checkout of the standard.
|
||||
|
||||
| reason | raised at |
|
||||
|---|---|
|
||||
| `not_conformant` | §4.1 rule 2 |
|
||||
| `suite_unknown` | §4.1 rule 3 |
|
||||
| `corpus_mismatch` | §4.1 rule 4 |
|
||||
|
||||
## 5. Reasons
|
||||
|
||||
The codes this extension defines, each with the step that raises it:
|
||||
The codes a verifier refuses a record with, each with the step of §4 that raises it (§4.1
|
||||
names its own):
|
||||
|
||||
| reason | raised at |
|
||||
|---|---|
|
||||
|
|
@ -139,6 +169,14 @@ digest out of form, bytes that are not JSON), each with the outcome §4 gives it
|
|||
`wai_conform_result_vectors verify` (in `wai-rs`, feature `conform_result`) and the independent
|
||||
`conform_result_verify.py`, which shares no code with `wai-rs`, reach every vector's outcome.
|
||||
|
||||
The rules of §4.1 are held by `wai-rs`'s `tests/cli_conform_sinks.rs`, over records of runs of the
|
||||
native sink, through its module and its C interface, and of the score decoder: each is admitted; a
|
||||
sink that presents an object whose digest does not match is refused `not_conformant`; the same sink
|
||||
run against a copy of the corpus changed to suit it is admitted by a party holding that copy and
|
||||
refused `corpus_mismatch` against WAI's; and a record of a suite the build does not run is refused
|
||||
`suite_unknown`. §4.1 has that reference implementation only: this corpus holds no admission
|
||||
vectors, and `conform_result_verify.py` does not apply its rules.
|
||||
|
||||
## 8. Security and privacy considerations
|
||||
|
||||
- **What a record reveals.** The command line and the paths of the files it names are the
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from one diverging bit, select a different distribution from the encoder's and
|
|||
corrupt the entropy decode. A float stage whose operations, order, precision and
|
||||
environment are all fixed does reproduce, within the implementation that fixes
|
||||
them; §5 describes one, and §2–§3 say why that is not a tier and does not back
|
||||
`replicate`. Three published routes to reproducibility are below. Two make the
|
||||
`replicate`. Five published routes to reproducibility are below. Two make the
|
||||
**entropy stage** reproducible while leaving the reconstruction to pixels to the
|
||||
implementation, within whatever criterion a conformance specification sets on it:
|
||||
|
||||
|
|
@ -54,14 +54,51 @@ implementation, within whatever criterion a conformance specification sets on it
|
|||
explicitly, through the hyperprior, so the decoder reads them instead of
|
||||
deriving them and the entropy decode is consistent across devices *without*
|
||||
bit-exact arithmetic. The side information costs bitrate. Same target: a
|
||||
reproducible entropy stage, a reconstruction that may vary.
|
||||
reproducible entropy stage, a reconstruction that may vary. The paper's body
|
||||
states the second half: "floating-point differences still cause gradual
|
||||
divergence in the features and reconstructed frames", and "without a
|
||||
synchronization mechanism, the divergence can grow arbitrarily large" (§3.2).
|
||||
It bounds the divergence with periodic I-frames that "fully resynchronize
|
||||
encoder and decoder feature states" (§3.2). Those I-frames are decoded by the
|
||||
same float model (Appendix B.8), so a resynchronisation moves the picture back
|
||||
to one float decode; it does not make the picture reproducible.
|
||||
|
||||
The third makes the reconstruction itself reproducible:
|
||||
The other three make the reconstruction itself reproducible, each by decoding in
|
||||
integer arithmetic:
|
||||
|
||||
- **A quantised decoder.** A published learned image codec (arXiv 2312.11209)
|
||||
quantises the weights and activations of all its decoder subnetworks, without
|
||||
accumulator overflow, so that the reconstructed image is deterministic across
|
||||
devices, and reports its output as bit-exact.
|
||||
- **Integerisation of a float model.** A published learned video codec
|
||||
(arXiv 2502.20762) runs its float model in a 16-bit integer mode without
|
||||
retraining: each value is mapped to round(512 · v), and its accumulators are
|
||||
32-bit, of which it reports that "no overflow issues have been observed"
|
||||
(§4.5). That is an observation, not a bound.
|
||||
- **Integer training.** A published learned video codec is trained as an
|
||||
integer-only model from the start, and claims coding consistency across
|
||||
platforms from integer decoding in the encoder's reconstruction loop
|
||||
(OpenReview `KCQo0fXtFH`, abstract). Its body and its code were not read here.
|
||||
|
||||
One more published line reaches neither tier: **float synchronised on a tested
|
||||
hardware class.** A streamable learned video codec (arXiv 2608.00483) decodes its
|
||||
P-frames in 16-bit float, and its I-frames and its periodic resets of a feature
|
||||
adapter in 32-bit float (§II-C, §II-D). It was tested on twelve GPUs of one
|
||||
vendor under one driver (Table II). In the mixed mode, a stream decoded only on
|
||||
GPUs of the generation that encoded it, and at 1080p not on every one of them; in
|
||||
pure 32-bit float, streams decoded across generations at 1080p and split into
|
||||
three groups at 4K (Table IV). A
|
||||
stream decoded outside its group fell from 26.3 dB to between 11.9 and 13.7 dB of
|
||||
luma PSNR at 1080p, and from 28.2 dB to between 7.3 and 10.6 dB at 4K, or the
|
||||
decoder did not complete (§II-F-3, Table IV). The authors give the cause SPEC §7
|
||||
gives (*Replicate-soundness*): pure 32-bit float "is strictly non-deterministic,
|
||||
with each GPU architecture generating a unique bitstream", because kernels
|
||||
"whose differing reduction orders break bit-exactness even under IEEE-754 FP32,
|
||||
which fixes per-operation rounding but not operation ordering" were chosen per
|
||||
generation (§III-B). Exactness found on the devices tested is a property of those
|
||||
devices and not of a capability (§2), and the frames between two resets have no
|
||||
portable hash, so this line is no tier of this document (§3, *No tier between the
|
||||
two*).
|
||||
|
||||
WAI names the two outcomes as tiers, so a capability states which one it makes, an
|
||||
envelope's `intent` can demand the one it needs (`replicate` over a neural
|
||||
|
|
@ -330,11 +367,57 @@ headroom and the rendition's digest in its rendition record (exact-tonemap §7).
|
|||
declaration, not the arithmetic. Neither step inspects the decode path a sink
|
||||
actually runs; for `wai.neural.int_synth` and `wai.neural.int_mlicpp`, the
|
||||
`NeuralIntegerExact` capabilities the reference neural dispatch decodes, there is
|
||||
only the integer path.
|
||||
only the integer path. The decode then reports the path that ran
|
||||
(`neural::Dispatched::path`), which a sink states in a receipt (§5).
|
||||
- **Receipts.** A receipt that carries a decoded-medium hash as reproducible by
|
||||
any conforming sink MUST come from a `decode-equivalence` capability. A hash
|
||||
over a reconstruction that one implementation pins (§5) is that
|
||||
implementation's, and a receipt MUST NOT present it as the capability's.
|
||||
- **No tier between the two (normative).** The tier vocabulary of a pin is closed:
|
||||
`decode-equivalence`, `entropy-consistency` and `none` (SPEC §3.1). No tier,
|
||||
registry class or claim states that a reconstruction stays within a bound of the
|
||||
exact one between points at which it is made exact again. Such a bound is
|
||||
measured on named devices and a named corpus, and a sink cannot check it from the
|
||||
bytes it received (§4); §1 gives the published line that reaches it. Three rules
|
||||
follow:
|
||||
1. A decode of a `NeuralIntegerExact` capability that runs any stage in reduced
|
||||
precision or in floating point is not a decode of that capability. A sink
|
||||
MUST NOT report it under that capability's identifier, in a receipt or in a
|
||||
claim. A faster decode of the same payload is registered as a capability of
|
||||
its own, with its own class, as `wai.audio.opus.dred` and
|
||||
`wai.audio.opus.dred.int` are two capabilities over one payload (SPEC §5,
|
||||
*Broadcast and streaming codecs*).
|
||||
2. A capability whose payload holds frames of more than one type, such as intra
|
||||
frames and predicted frames, takes the class, and with it the tier, of its
|
||||
weakest frame type. A pin's `tier` MUST NOT state more than that class gives
|
||||
(SPEC §3.1, `tier_exceeds_class`). A frame of such a payload decoded exactly
|
||||
does not raise the tier of the capability.
|
||||
3. A clip digest (SPEC §7, *Digests*), a stage hash
|
||||
([staged-delivery](staged-delivery.md) §5), or a reconstruction binding over
|
||||
a sequence of frames, covers only frames that were each decoded at
|
||||
`decode-equivalence`. A sequence in which any frame was decoded at a weaker
|
||||
tier, or by a path that does not compute the registered reconstruction, has
|
||||
no such digest. Each frame of it that was decoded exactly keeps its own. A
|
||||
receipt is checked per object, not per frame: a stage hash is refused where any
|
||||
decoder entry decoded the set's base at a weaker tier, or the base or a layer of
|
||||
the set on a path that is not exact, whichever units that entry presented
|
||||
([jwp-receipts](jwp-receipts.md) §2.4.12, step 6 against the stage claims,
|
||||
item 5). So the outcome does not depend on whether the verifier holds the
|
||||
outputs, which alone say what each entry presented.
|
||||
|
||||
These rules govern decoded media. A generated medium's tier,
|
||||
generation-equivalence (SPEC §7, `GenerativeDeterministic`), is not a pin's tier:
|
||||
it carries its own digest of the frames generated
|
||||
([synthesis](synthesis.md) §9.2; [avatars](avatars.md) §7.7), and rule 3 does not
|
||||
void it.
|
||||
|
||||
A receipt of a decode states the path that ran ([jwp-receipts](jwp-receipts.md)
|
||||
§2.4.12, a decoder entry's `variant`), and the claim corpus refuses a receipt
|
||||
that breaks each rule: a `NeuralIntegerExact` capability named with a path that
|
||||
is not exact (rule 1); a pin's `tier` above its capability's registry class,
|
||||
which is that of its weakest frame type, and above what the path that ran holds
|
||||
(rule 2); and a stage hash over a base or a layer one of whose decoder entries
|
||||
decoded it at a weaker tier or on a path that is not exact (rule 3).
|
||||
- **Post-decode processing.** A tier claim is void if the sink alters the medium
|
||||
after decode (§2, *What a tier covers*). This applies to the emitted result,
|
||||
not to the decoder's internal conformance.
|
||||
|
|
@ -450,8 +533,17 @@ this configuration rather than decoding it to the wrong image. Within it, the ex
|
|||
decoder runs when the sink supplies its integer weight bundle and the thread's
|
||||
floating-point environment is the IEEE-754 default. Otherwise the reference sink
|
||||
decodes the same capability ID through the float ONNX Runtime path, whose
|
||||
reconstruction is not pinned. Both paths return the same result type, and the
|
||||
reference dispatch does not report which one ran.
|
||||
reconstruction is not pinned. The reference dispatch reports which path ran:
|
||||
`neural::decode_envelope` returns, with the image, the capability it decoded
|
||||
through and the path, `DecodePath::Exact` or `DecodePath::Float`. A sink states that
|
||||
path in a `reconstruct-session` claim as its decoder entry's `variant`, `exact` or
|
||||
`float` ([jwp-receipts](jwp-receipts.md) §2.4.12), and the reference check of a
|
||||
reconstruction binding reads it (`neural::check_reconstruction_binding`,
|
||||
[reconstruction-binding](reconstruction-binding.md) §2).
|
||||
`wai-rs/tests/jpegai_decode_path.rs` runs each path and requires the result, the
|
||||
claim and the binding check to say which ran. CI runs the exact decoder over the
|
||||
pinned bundle; the float path's model bundle is not in the repository, so its case
|
||||
runs locally only, on a host that holds it.
|
||||
|
||||
On both paths the stages the capability's claim covers, the scale path and the two
|
||||
entropy decodes, run through the integer operator set, by the lowering `jpegai-int/1`
|
||||
|
|
@ -533,7 +625,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)
|
||||
|
|
@ -934,6 +1029,13 @@ record, recomputes each from the readings and checks it, in CI.
|
|||
Each figure holds for the pictures, models, platforms and builds the record names, and for
|
||||
nothing else (§7.2).
|
||||
|
||||
Two published records of float decode across devices are cited in §1. Neither is part of
|
||||
this record. arXiv 2608.00483 found its streams to decode only within the groups of GPUs
|
||||
its tests established (Table IV). arXiv 2606.28027 found its float reconstruction to diverge
|
||||
across devices unless it is resynchronised (§3.2), and its 8-bit integer decode not to be
|
||||
bit-exact across neural processors, for "non-standard implementations of rounding and
|
||||
multiplication in quantize/dequantize and re-quantization" (Supplement D.1).
|
||||
|
||||
What the record does not cover is listed in its §1: `wai.image.jpegai`'s exact decoder
|
||||
(one configuration, so one rate point and no curve; and the reference set was not coded
|
||||
with the standard's reference encoder), `wai.neural.int_synth` and `wai.neural.int_mlicpp`
|
||||
|
|
|
|||
|
|
@ -1035,12 +1035,17 @@ A **decoder entry**:
|
|||
| `first_unit` | integer | The title's unit that is unit 0 of the object's base: of the object itself for a base or a rendition with no `refines`, and of the base it refines for a layer. A presentation through the entry of the title's unit `u` is of the object's unit `u − first_unit`. Where a verifier holds a layer's envelope and an entry names its base, the layer's entry has that entry's `first_unit`; where it holds a `stage` claim of a set, an entry naming the set's base or a layer the claim lists has the unit `stage_sets` names that claim at (step 6). |
|
||||
| `path` | string | `primary` or `fallback`. |
|
||||
| `decoder` | string | `builtin:<crate>@<version>` — `<crate>` 1 to 64 of `a`–`z`, `0`–`9`, `_` and `-`, `<version>` 1 to 64 of ASCII letters, digits, `.`, `+` and `-`, beginning with a digit — or `module:sha256:` followed by a module's digest (§7.9), 64 lowercase hexadecimal digits. |
|
||||
| `variant` | term or `null` | A decode path within the decoder, where it has more than one, such as an exact and a floating-point path. |
|
||||
| `pin` | 32 B hex or `null` | The `sha256` of the parameter-set pin the sink verified (SPEC §3.1). |
|
||||
| `tier` | string or `null` | That pin's declared tier as SPEC §3.1 reads it: `decode-equivalence`, `entropy-consistency` or `none`. `null` exactly when `pin` is. |
|
||||
| `variant` | term or `null` | The decode path within the decoder that ran, where the capability has more than one: `exact` for the path that computes the reconstruction the capability registers (for `wai.image.jpegai`, its exact decoder), or `float` for a path that runs a stage in floating point the registration does not fix (for `wai.image.jpegai`, its float decode) ([determinism-tiers](determinism-tiers.md) §5). `null` for a capability with one path. A `NeuralIntegerExact` capability's integer path may also be named by the lowering its registration names ([integer-operators](integer-operators.md) §11, its `ops` member's `lowering`), as a `present` entry of [opus-dred](opus-dred.md) §7.2 names `dred-int/1` ([synthesis](synthesis.md) §10.3). The entry states an **exact path** when its `variant` is `null`, `exact`, or, for a capability of the replicate class `neural-int-exact`, that lowering; any other term, `float` among them, names a path that is not exact. A sink states the path that ran, as the reference dispatch reports it, not the tier its capability is registered at. |
|
||||
| `pin` | 32 B hex or `null` | The parameter set the decode read, by the digest a pin over it names (SPEC §3.1, *Pin by default*): the `sha256` of the pin the sink verified; for a decode that verified no pin, the digest a pin over the bytes it read would name, which is the SHA-256 of the one file of a capability registered with a one-file set, and otherwise the SHA-256 of the listing (`wai.prior.set`) of the files it read. `null` exactly when the decode read no parameter set. |
|
||||
| `tier` | string or `null` | That pin's declared tier as SPEC §3.1 reads it: `decode-equivalence`, `entropy-consistency` or `none`; `none` for a decode that verified no pin, which nothing declared. `null` exactly when `pin` is. |
|
||||
| `module_status` | string or `null` | For a `module:` decoder, the anchor its grant was confirmed under (§7.9): `pinned` or `fetched`. `null` for a `builtin:` decoder. |
|
||||
| `leaves` | integer | The presentations decoded through the entry. |
|
||||
|
||||
A decoder entry sealed by a sink of an earlier text of this extension, under which `pin`
|
||||
named only a pin the sink verified, may carry `pin` and `tier` `null` for an unpinned
|
||||
decode that read a parameter set. A verifier cannot tell such an entry from one whose decode
|
||||
read no set.
|
||||
|
||||
`energy` is the decode energy of one completion of the presentations the claim commits
|
||||
to — the decodes behind its `leaf_count` leaves, the work unit
|
||||
[staged-measurement](staged-measurement.md) §8 names (its §8.1) — on the host's
|
||||
|
|
@ -1088,6 +1093,17 @@ Rules, checked at §3.1 step 4 in this order:
|
|||
(`energy_zero_figure`), and its class is admitted as §2.4.5's rule 2 admits one
|
||||
(`energy_unknown_acquisition`, `energy_legacy_acquisition`,
|
||||
`energy_acquisition_evidence`).
|
||||
13. A decoder entry whose `capability` has the replicate class `neural-int-exact`
|
||||
(SPEC §7) **MUST** state an exact path: `variant` `null`, `exact` or the lowering its
|
||||
registration names (`session_variant_not_exact`). A decode of such a capability by
|
||||
any other path is not a decode of it ([determinism-tiers](determinism-tiers.md) §3,
|
||||
*No tier between the two*, rule 1).
|
||||
14. A decoder entry that does not state an exact path **MUST NOT** have `tier`
|
||||
`decode-equivalence` (`session_tier_exceeds_variant`). Frames a path that is not
|
||||
exact decoded are not decode-equivalent, whatever the pin declares
|
||||
(determinism-tiers §3, rule 2). Rules 6 and 13 already refuse such an entry for every
|
||||
capability the registry classifies; this rule reaches the others, such as a capability
|
||||
a module decodes.
|
||||
|
||||
**Against the outputs** (§3.1 step 6). The context is the list of presentations, each
|
||||
`{ "unit", "layers", "decoder", "out_digest" }`: a unit, the layers it was presented
|
||||
|
|
@ -1258,7 +1274,18 @@ epochs, and bases that cover the same units of the title each have their own. In
|
|||
one lists, has as its `first_unit` the `first_unit` of an entry of `stage_sets` naming
|
||||
a claim of that object (`session_first_unit_mismatch`): a set's base and its layers
|
||||
are at the units the session names the set at;
|
||||
5. where the verifier holds the outputs as well, for each claim and each entry of
|
||||
5. each of them that states a `stage_hash`, a clip digest over its set's units
|
||||
([staged-delivery](staged-delivery.md) §5), has every decoder entry whose `object` is
|
||||
its `subject` at `decode-equivalence` by that section's base step (a capability of
|
||||
the replicate class `standard-defined`, or of `neural-int-exact` with `tier`
|
||||
`decode-equivalence`), and every decoder entry whose `object` is its `subject` or a
|
||||
layer object it lists states an exact path (`session_stage_hash_not_exact`). A
|
||||
sequence any frame of which was decoded at a weaker tier, or by a path that is not
|
||||
exact, has no clip digest ([determinism-tiers](determinism-tiers.md) §3, *No tier
|
||||
between the two*, rule 3). The check is per object: an entry that names the base or
|
||||
such a layer counts whichever units it presented, so the outcome is the same whether
|
||||
or not the verifier holds the outputs;
|
||||
6. where the verifier holds the outputs as well, for each claim and each entry of
|
||||
`stage_sets` naming it, with `f` that entry's `first_unit` and `n` the units the claim
|
||||
counts (`session_stage_disagrees`):
|
||||
- every unit from `f` to `f + n − 1` is presented;
|
||||
|
|
@ -1267,7 +1294,7 @@ epochs, and bases that cover the same units of the title each have their own. In
|
|||
unit presented on time at a stage was presented at every lower stage before it, and
|
||||
late presentations are leaves of the tree as well, so the tree can hold more than a
|
||||
`stage` claim counts and never less;
|
||||
6. where it holds the outputs, from above: every unit of an entry's units presented at
|
||||
7. where it holds the outputs, from above: every unit of an entry's units presented at
|
||||
more than `s` layers lies in the span of a layer-`s` object that a claim named by an
|
||||
entry holding the unit lists as `applied` or `partial` (`session_stage_disagrees`). A
|
||||
unit reaches a stage only through a layer the sink applied: a `late` layer missed the
|
||||
|
|
@ -2285,6 +2312,8 @@ and never folds it in: confirmation changes the outcome of no step here.
|
|||
| 4 | `session_stage_sets_invalid` | `stage_sets` out of order, or an entry twice |
|
||||
| 4 | `session_counted_invalid` | `counted_discarded` above `counted_primary` and `counted_fallback` together |
|
||||
| 4 | `session_classical_invalid` | `classical_source` present without `classical_declared` or absent with it, or not `wai2-table` |
|
||||
| 4 | `session_variant_not_exact` | a decoder entry names a capability of the replicate class `neural-int-exact` and does not state an exact path: a `variant` other than `null`, `exact` or the lowering its registration names |
|
||||
| 4 | `session_tier_exceeds_variant` | a decoder entry does not state an exact path, and its `tier` is `decode-equivalence` |
|
||||
| 4 | `consumption_objects_unordered` | a `consumption` claim's `objects` are not in strictly ascending order ([ai-usage](ai-usage.md) §6.2) |
|
||||
| 4 | `consumption_subject_mismatch` | its `subject` is not the first of its `objects` |
|
||||
| 4 | `consumption_usage_unordered` | its `usage` entries are not in strictly ascending order of `vocabulary` |
|
||||
|
|
@ -2352,6 +2381,7 @@ and never folds it in: confirmation changes the outcome of no step here.
|
|||
| 6 | `schedule_content_mismatch` | an entry's `content_hash` is not that of the object receipt the verifier holds at its position |
|
||||
| 6 | `schedule_length_mismatch` | an entry's transfer length is not the length of the object's envelope the verifier holds |
|
||||
| 6 | `session_stage_disagrees` | a `stage` claim the session names is about a base, or lists an arrived layer object, the session did not receive; counts a unit the session never presented, or more units at a stage than the session presented at it or above; or no claim whose set holds a unit lists an applied layer that reaches a stage the session presented it at |
|
||||
| 6 | `session_stage_hash_not_exact` | a `stage` claim the session names states a `stage_hash`, and a decoder entry names its base at a step weaker than `decode-equivalence`, or names its base or a layer it lists without stating an exact path, whichever units it presented |
|
||||
|
||||
### 3.2. Recovery through a soft binding, and the ancestor rule
|
||||
|
||||
|
|
@ -2958,7 +2988,10 @@ fewer than it counts; a base forgotten and decoded again (`staged-conformance/`'
|
|||
every reason of §2.4.12's rules and
|
||||
checks, the caps of the switches and missed presentations written at and past them; a
|
||||
metered session's figure of zero (`energy_zero_figure`) and every acquisition-class
|
||||
reason; and step 2's enumerations and order. `claim_verify.py` gives every outcome.
|
||||
reason; and step 2's enumerations and order. The decode path an entry states has a
|
||||
refusal for each of rules 13 and 14 and for a stage hash over a base or layer decoded at
|
||||
a weaker tier or by a path that is not exact, beside accepted entries for each of
|
||||
`wai.image.jpegai`'s two paths and for an integer path named by its registered lowering. `claim_verify.py` gives every outcome.
|
||||
`differential.py` generates sessions around every rule — units, leaves and paths off by
|
||||
one, switches and missed presentations at and past their caps, a `null` deadline with
|
||||
misses, presences that disagree, decoder indices out of range, leaves moved between
|
||||
|
|
|
|||
|
|
@ -353,6 +353,11 @@ version.
|
|||
and every one of its checks dropped.
|
||||
- A native integration's own tests drive it through its platform's player and require that every
|
||||
object the player was handed is one the sink presented, and that the bundle verifies.
|
||||
- `wai conform sink --suite native-sink` runs the corpus against a sink, each case with its answers
|
||||
withheld, and writes the run's outcome as a signed `conform-result/1` record
|
||||
([conformance-results](conformance-results.md)). WAI's own sink is run so in CI, through the
|
||||
reference module and through the C interface of §9, and each record must be admitted
|
||||
(conformance-results §4.1).
|
||||
|
||||
## 11. Security and privacy considerations
|
||||
|
||||
|
|
|
|||
|
|
@ -505,6 +505,12 @@ for each run of lost packets it presents, a `present` entry whose
|
|||
- `output` is the digest of that run's reconstruction buffer (§5.3), `origin` and `class` as
|
||||
synthesis §10.3 has them (`null` where the player applies no claim).
|
||||
|
||||
That `variant` is the lowering `wai.audio.opus.dred.int`'s registration names. A
|
||||
`reconstruct-session` claim of the same decode may name its decoder entry's path by it, by
|
||||
`exact` or by `null`: each is an exact path of a `NeuralIntegerExact` capability
|
||||
(jwp-receipts §2.4.12, `variant`). Any other term is not, and the entry is refused
|
||||
(`session_variant_not_exact`).
|
||||
|
||||
A verifier checks the log as synthesis §10.3 does (`record_malformed`, `bad_key`, `bad_signature`,
|
||||
`player_chain_broken`), then each `present` entry: its decoder, variant and tier are those above,
|
||||
its subject is the content hash of the object that recovers a run of the stream, and its `output`
|
||||
|
|
|
|||
|
|
@ -103,9 +103,14 @@ Conformance criterion:
|
|||
and then MUST obtain the recorded `hash`. A verifier that decodes any other way
|
||||
has not checked the binding: a mismatch it finds is not evidence that the asset
|
||||
changed, and it MUST NOT report the binding as failed on that basis. The
|
||||
reference dispatch (`decode_envelope`) does not report which path ran, so a
|
||||
verifier calls the exact decoder directly (`jpegai_decode::decode`), which
|
||||
refuses rather than falls back.
|
||||
reference dispatch (`neural::decode_envelope`) reports the path that ran
|
||||
([determinism-tiers](determinism-tiers.md) §5), and the reference check
|
||||
(`neural::check_reconstruction_binding`) reads it: over the exact decoder it
|
||||
reports whether the hash holds, and over the float fallback it reports the
|
||||
binding as not checked, with the path, never as failed. It reads the descriptor
|
||||
too: a binding whose `decoderId` is not 2, whose `format` is not `RGB8`, or whose
|
||||
`width` and `height` are not the decoded picture's names a reconstruction the
|
||||
exact decoder does not compute, and is reported as such, never as failed.
|
||||
|
||||
## 3. C2PA / JPEG Trust carriage
|
||||
|
||||
|
|
|
|||
|
|
@ -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,56 @@ 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 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 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:
|
||||
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -607,8 +607,10 @@ node = SHA-256("wai:out-node\x01" ‖ left ‖ right)
|
|||
- `decoder` is the index of the decoder entry that produced the output: entries are
|
||||
(capability, pin, object) triples in first-use order, presentations taken by (layers,
|
||||
unit), the object being the content hash ([jwp-receipts](jwp-receipts.md) §2.1) of
|
||||
the envelope decoded. For a title, each base's capability with the pin it verified and
|
||||
its envelope, then each layer's refinement capability and envelope. For a session
|
||||
the envelope decoded. For a title, each base's capability with the parameter set its
|
||||
decode read, named as a decoder entry's `pin` names it (jwp-receipts §2.4.12: the pin it
|
||||
verified, where it verified one), and its envelope, then each layer's refinement
|
||||
capability and envelope. For a session
|
||||
claim, the entries are its decoder entries (jwp-receipts §2.4.12), in the same order.
|
||||
- Leaves are ordered by (unit, layers), and a (unit, layers) given twice is refused.
|
||||
- The root over `n ≥ 1` leaves is RFC 6962's Merkle tree hash: one leaf is itself;
|
||||
|
|
|
|||
|
|
@ -52,3 +52,13 @@ Rebuilding: `cd wai/wai-rs && cargo run --no-default-features --features native_
|
|||
wai_native_sink_vectors gen ../live-conformance/native-sink`; `… verify ../live-conformance/native-sink`
|
||||
checks it; `python3 -I ../live-conformance/native_sink_verify.py ../live-conformance/native-sink` is
|
||||
the second implementation.
|
||||
|
||||
**Running a sink against it.** `wai conform sink --suite native-sink --impl "<command> {case}"` gives a
|
||||
sink each case with its answers withheld: the bodies its calls name (`objects`, hexadecimal), its
|
||||
`config`, `seed` and `start_us`, and each call's `do`; and each configuration under `configs` as a case
|
||||
of no call, under a seed of zeros. The sink writes its answer to each call in order, or, refusing the
|
||||
configuration, that refusal alone, one JSON text a line in RFC 8785 form. A case passes when what it
|
||||
writes has the SHA-256 of the answers this file states, written the same way, and the run's outcome is
|
||||
a signed `conform-result/1` record ([conformance-results](../../extensions/conformance-results.md)).
|
||||
`wai_native_sink_vectors answers {case}` is the reference sink, and `… answers --c-abi {case}` the same
|
||||
sink through its C interface.
|
||||
|
|
|
|||
|
|
@ -31,6 +31,11 @@ for every case:
|
|||
`tier_unregistered`, `supersedes_malformed`, `form_not_registered`,
|
||||
`form_required`, `tier_exceeds_class`, a `replicate_*` reason), or `ok`. For a
|
||||
`WAI2` manifest the finding is `[entry index, reason]`.
|
||||
- `unpinned.json` — the report a verifier makes about a manifest that pins no
|
||||
parameter set where a capability it decodes through is registered as decoding
|
||||
against one (SPEC §3.1, *Pin by default*): `prior_unpinned`, or `none`. It is
|
||||
informative, and made whatever `static.json` finds. For a `WAI2` manifest, the index
|
||||
of each entry it is made about.
|
||||
|
||||
**Listings.** Every `listings/<name>.sha256` is, byte for byte, the listing of
|
||||
`sets/<name>/`: regular files, names that begin with `.` left out, sorted by path as
|
||||
|
|
@ -44,9 +49,12 @@ Files are compared as bytes; `.gitattributes` turns off line-ending conversion.
|
|||
|
||||
- `wai_pin_vectors verify pin-conformance` (wai-rs) runs every file through the
|
||||
reference implementation's in-memory pin core (`wai::pin::verify_bytes`), its
|
||||
selection (`WaiMulti::pick`) and its static checks (`Wai::check_pin`,
|
||||
`WaiMulti::check_pins`). It runs in both corpus loops of the conformance workflow.
|
||||
selection (`WaiMulti::pick`), its static checks (`Wai::check_pin`,
|
||||
`WaiMulti::check_pins`) and its report (`Wai::check_unpinned`,
|
||||
`WaiMulti::check_unpinned`). It runs in both corpus loops of the conformance workflow.
|
||||
`wai_pin_vectors generate pin-conformance` rewrites the listings from the sets.
|
||||
- `pin_verify.py` checks `cases.json` and `listings/` with the Python standard
|
||||
library alone, re-implementing the listing rules and the ordered checks from the
|
||||
SPEC text. It shares no code with wai-rs.
|
||||
- `pin_verify.py` checks `cases.json`, `unpinned.json` and `listings/` with the Python
|
||||
standard library alone, re-implementing the listing rules, the ordered checks and the
|
||||
report from the SPEC text, and reading which capabilities decode against a parameter
|
||||
set from the published registry export (`../registry/capabilities.json`). It shares no
|
||||
code with wai-rs.
|
||||
|
|
|
|||
|
|
@ -8,13 +8,18 @@ library only, and checks that
|
|||
- every `listings/<name>.sha256` is byte for byte the listing of `sets/<name>/`
|
||||
(regular files, names that begin with `.` skipped), and every file under
|
||||
`listings/bad/` breaks a listing rule;
|
||||
- every case in `cases.json` gets its declared reason.
|
||||
- every case in `cases.json` gets its declared reason;
|
||||
- every manifest in `unpinned.json` gets its declared report (SPEC.md §3.1,
|
||||
Pin by default): `prior_unpinned` where it pins no parameter set and a
|
||||
capability it decodes through is registered as decoding against one.
|
||||
|
||||
python3 pin-conformance/pin_verify.py [corpus-dir]
|
||||
|
||||
The registry facts it needs are restated here from SPEC.md §5 and §3.1: the
|
||||
registered prior forms, the forms the reference sink reads, and the capabilities
|
||||
registered with a one-file parameter set.
|
||||
registered with a one-file parameter set. Which capabilities decode against a
|
||||
parameter set it reads from the published registry export,
|
||||
`../registry/capabilities.json`: those of the four classes SPEC.md §3.1 names.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
|
|
@ -28,6 +33,10 @@ ONE_FILE = {
|
|||
'wai.neural.int_synth', 'wai.neural.int_mlicpp', 'wai.neural.encodec32', 'wai.neural.dac',
|
||||
'wai.neural.mimi', 'wai.neural.wavtokenizer', 'wai.neural.snac', 'wai.neural.video_bmshj2018',
|
||||
}
|
||||
# SPEC.md §3.1: the registry classes whose capabilities decode against a parameter set.
|
||||
SET_CLASSES = {'neural-int-exact', 'neural-entropy-exact', 'neural-float', 'generative-exact'}
|
||||
# SPEC.md §3.1: the one capability whose payload names its own parameter set.
|
||||
NAMES_ITS_SET = 'wai.generate.spectral'
|
||||
HEX64 = re.compile(r'^[0-9a-f]{64}$')
|
||||
SEGMENT = re.compile(r'^[A-Za-z0-9_-]([A-Za-z0-9_.-]*[A-Za-z0-9_-])?$')
|
||||
|
||||
|
|
@ -145,6 +154,29 @@ def load(corpus, held):
|
|||
return out
|
||||
|
||||
|
||||
def registry_classes():
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'registry', 'capabilities.json')
|
||||
with open(path, encoding='utf-8') as f:
|
||||
return {c['capability']: c['replicate'] for c in json.load(f)['capabilities']}
|
||||
|
||||
|
||||
def pin_by_default(cap, classes):
|
||||
"""Whether an encoder SHOULD pin the set a payload of `cap` was coded against."""
|
||||
return classes.get(cap) in SET_CLASSES and cap != NAMES_ITS_SET
|
||||
|
||||
|
||||
def unpinned(manifest, classes):
|
||||
"""The report of SPEC.md §3.1, Pin by default: a WAI1 manifest's, or each WAI2 entry's."""
|
||||
if manifest['wai'] == '1.0':
|
||||
if manifest['conditioning'].get('prior') is not None:
|
||||
return 'none'
|
||||
mr = manifest['model_requirement']
|
||||
caps = [mr['capability']] + ([mr['fallback']] if mr.get('fallback') is not None else [])
|
||||
return 'prior_unpinned' if any(pin_by_default(c, classes) for c in caps) else 'none'
|
||||
return [i for i, e in enumerate(manifest['renditions'])
|
||||
if e.get('prior') is None and pin_by_default(e['capability'], classes)]
|
||||
|
||||
|
||||
def main():
|
||||
corpus = sys.argv[1] if len(sys.argv) > 1 else os.path.dirname(os.path.abspath(__file__))
|
||||
fails, n = [], 0
|
||||
|
|
@ -174,6 +206,13 @@ def main():
|
|||
got = verify(c['capability'], pin, load(corpus, c['held']), c.get('readable', READABLE))
|
||||
if got != c['expect']:
|
||||
fails.append(f"cases.json {c['name']}: expected {c['expect']}, got {got}")
|
||||
classes = registry_classes()
|
||||
with open(os.path.join(corpus, 'unpinned.json')) as f:
|
||||
for c in json.load(f)['cases']:
|
||||
n += 1
|
||||
got = unpinned(c['manifest'], classes)
|
||||
if got != c['expect']:
|
||||
fails.append(f"unpinned.json {c['name']}: expected {c['expect']}, got {got}")
|
||||
for f in fails:
|
||||
print('FAIL', f)
|
||||
print(f'pin_verify.py: {n - len(fails)} of {n} checks as declared')
|
||||
|
|
|
|||
265
wai/pin-conformance/unpinned.json
Normal file
265
wai/pin-conformance/unpinned.json
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
{
|
||||
"about": "SPEC.md \u00a73.1, Pin by default: the report a verifier makes about a manifest that pins no parameter set where a capability it decodes through is registered as decoding against one (\"prior_unpinned\"), or \"none\". It is informative: every manifest here conforms but the one that claims replicate over an integer-exact capability unpinned, which static.json refuses. For a WAI2 manifest, the index of each entry it is made about.",
|
||||
"cases": [
|
||||
{
|
||||
"name": "integer_exact_improve",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"model_requirement": {
|
||||
"capability": "wai.neural.int_mlicpp"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "integer_exact_replicate",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "replicate",
|
||||
"model_requirement": {
|
||||
"capability": "wai.neural.int_synth"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "float_improve",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"model_requirement": {
|
||||
"capability": "wai.neural.bmshj2018"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "entropy_exact_improve",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"model_requirement": {
|
||||
"capability": "wai.image.jpegai"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "generator_with_a_set_create",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "create",
|
||||
"model_requirement": {
|
||||
"capability": "wai.avatar.face"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "null_prior_is_absent",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"model_requirement": {
|
||||
"capability": "wai.neural.int_mlicpp"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k",
|
||||
"prior": null
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "the_fallback_decodes_against_a_set",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"model_requirement": {
|
||||
"capability": "wai.image.png",
|
||||
"fallback": "wai.neural.bmshj2018"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "prior_unpinned"
|
||||
},
|
||||
{
|
||||
"name": "pinned",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "replicate",
|
||||
"model_requirement": {
|
||||
"capability": "wai.neural.int_mlicpp"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k",
|
||||
"prior": {
|
||||
"id": "int_mlicpp/synthetic-s20260929",
|
||||
"sha256": "7ef44f3b1fc4a2df6c3e3b68460f595cae3d31cb7f9018699e29e738340272a4",
|
||||
"determinism": "int-rans/p16+int-mlicpp/i64"
|
||||
}
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "none"
|
||||
},
|
||||
{
|
||||
"name": "standard_codec",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "replicate",
|
||||
"model_requirement": {
|
||||
"capability": "wai.image.png"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "none"
|
||||
},
|
||||
{
|
||||
"name": "generator_names_its_model",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "create",
|
||||
"model_requirement": {
|
||||
"capability": "wai.generate.spectral"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "none"
|
||||
},
|
||||
{
|
||||
"name": "unregistered_capability",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"model_requirement": {
|
||||
"capability": "wai.neural.future"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "none"
|
||||
},
|
||||
{
|
||||
"name": "reads_no_set",
|
||||
"manifest": {
|
||||
"wai": "1.0",
|
||||
"media": "image",
|
||||
"intent": "replicate",
|
||||
"model_requirement": {
|
||||
"capability": "wai.video.int_motion"
|
||||
},
|
||||
"conditioning": {
|
||||
"kind": "k"
|
||||
},
|
||||
"target": null
|
||||
},
|
||||
"expect": "none"
|
||||
},
|
||||
{
|
||||
"name": "wai2_each_entry",
|
||||
"manifest": {
|
||||
"wai": "1.1",
|
||||
"media": "image",
|
||||
"intent": "improve",
|
||||
"renditions": [
|
||||
{
|
||||
"capability": "wai.image.png",
|
||||
"kind": "png"
|
||||
},
|
||||
{
|
||||
"capability": "wai.neural.int_mlicpp",
|
||||
"kind": "int_mlicpp"
|
||||
},
|
||||
{
|
||||
"capability": "wai.neural.int_mlicpp",
|
||||
"kind": "int_mlicpp",
|
||||
"prior": {
|
||||
"id": "int_mlicpp/synthetic-s20260929",
|
||||
"sha256": "7ef44f3b1fc4a2df6c3e3b68460f595cae3d31cb7f9018699e29e738340272a4",
|
||||
"determinism": "int-rans/p16+int-mlicpp/i64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability": "wai.neural.bmshj2018",
|
||||
"kind": "bmshj2018"
|
||||
}
|
||||
],
|
||||
"target": null
|
||||
},
|
||||
"expect": [
|
||||
1,
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "wai2_none",
|
||||
"manifest": {
|
||||
"wai": "1.1",
|
||||
"media": "image",
|
||||
"intent": "replicate",
|
||||
"renditions": [
|
||||
{
|
||||
"capability": "wai.neural.int_mlicpp",
|
||||
"kind": "int_mlicpp",
|
||||
"prior": {
|
||||
"id": "int_mlicpp/synthetic-s20260929",
|
||||
"sha256": "7ef44f3b1fc4a2df6c3e3b68460f595cae3d31cb7f9018699e29e738340272a4",
|
||||
"determinism": "int-rans/p16+int-mlicpp/i64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"capability": "wai.image.png",
|
||||
"kind": "png"
|
||||
}
|
||||
],
|
||||
"target": null
|
||||
},
|
||||
"expect": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ work.
|
|||
| ARIB STD-B60 (MMT) | v2.0 (2025-03-25), the main deployed MMT profile | **R** ⚠ |
|
||||
| NHK STRL Open House 2026 | C2PA provenance across capture, edit and distribution, with hardened in-browser verification. Cloud-unified broadcast/online production on ST 2110 + CMAF low-latency (**no MoQ or QUIC**). Live 8K 360-degree coding on VVC | **R** ⚠ |
|
||||
| a learned image coding line with quantised arithmetic since 2020 | arXiv 2007.04684 (*fixed point weight quantization*), 2111.09348 (*quantized weights and activations*). arXiv metadata carries no affiliation, so the institution the sweep attached to this line is **not verified** | P✓ (titles) / ⚠ (affiliation) |
|
||||
| arXiv 2608.00483 (2026-08; GLOBECOM 2026), by authors overlapping the line above | mixed-precision cross-platform neural video coding. P-frames run in FP16. I-frames and periodic feature-adapter resets run in IEEE-754 FP32, keeping the arithmetic entropy coder synchronised across GPUs. Evaluated across 12 GPUs of four architecture generations. Claims "deterministic synchronization at critical boundaries" and cross-generation decodability "for recent architectures". **The abstract makes no bit-exactness or identical-reconstruction claim** | P✓ (abstract) |
|
||||
| arXiv 2608.00483 (2026-08; GLOBECOM 2026), by authors overlapping the line above | mixed-precision cross-platform neural video coding. P-frames run in FP16. I-frames and periodic feature-adapter resets run in IEEE-754 FP32, keeping the arithmetic entropy coder synchronised across GPUs. Evaluated across 12 GPUs of four architecture generations. Claims "deterministic synchronization at critical boundaries" and cross-generation decodability "for recent architectures". **The abstract makes no bit-exactness or identical-reconstruction claim.** *Corrected 2026-11-03, body read:* that holds of the abstract only. The body claims bit-exact bitstream hashes within a GPU generation and identical decoded output wherever a decode succeeds (§III-B), and "bit-exact synchronization at critical GOP boundaries" (§IV). The 12 GPUs are of one vendor, under one driver (Table II). In the mixed mode a stream never decodes across a generation; the cross-generation decodability at 1080p belongs to the pure-FP32 mode, which splits into three groups at 4K (Table IV) | P✓ (abstract; body 2026-11-03) |
|
||||
| METI Top Runner, TV target FY2026 | per-device annual kWh | **S** ⚠ |
|
||||
|
||||
The 2030 landscape (§7.3) rejected a clause-level claim about a Japanese
|
||||
|
|
@ -690,9 +690,9 @@ review.
|
|||
| G4 | **WAI's `HwShunt` takes in IOReport, a reading the monorepo classes five ways; and neither WAI nor the family's receipt ladder separates platform-relayed counters from independently rooted instruments. Only `sandbox-tee` does.** Three layers. **(a) The family's receipt ladder.** Every standard that lists interfaces under `HwShunt` puts on-die counters and dedicated shunts in it together (JCR-1 §2.3, JCP §5.1, Sandbox §6.3, `mesh-schema`, `proof-schema`), and IOReport is classed inconsistently (§5.3). **(b) `sandbox-tee`'s `CounterClass`** reserves `HwShunt` for a dedicated shunt and gives RAPL, NVML, IOReport and Redfish classes of their own; its `AttestTrust` ladder is derived from evidence, and `efficiency/1` commits to the attestation (§5.1). **(c) WAI** has `HwShunt` cover "RAPL, NVML, SMC, IOReport, or an external shunt", and `wai_video_meter` stamps IOReport readings `HwShunt`. That places WAI with Joule Code's text and the Sandbox meter, and against JCR-1 §2.3. WAI's texts present the ladder as the family's own without noting the split: `video_receipt.rs` says it is "reused verbatim from `mesh-schema` / `proof-schema`", which list no IOReport; `energy_binding.rs` calls it "the family's ladder"; `energy-measurement.md` §2 says WAI reuses "the same three names the receipt families elsewhere in the ecosystem already use". At every layer but (b), a verifier cannot tell a platform-relayed counter, which may be deliberately filtered (§5.1), from an independently rooted instrument. | §5.3 (the per-standard survey); `wai/extensions/energy-measurement.md` §2; `wai-rs/src/energy_binding.rs`:30, :47–51 (`Acquisition`); `wai-rs/src/video_receipt.rs` `Provenance` doc comment; `wai-rs/src/bin/wai_video_meter.rs`:184–190; `jouleclaw/jouleclaw-rs/crates/jouleclaw-jcr1/docs/JCR-1-spec.md` §2.3; `jcp/SPEC.md` §5.1; `sandbox/SPEC.md` §6.3–§6.4; `mesh/mesh-rs/crates/mesh-schema/src/lib.rs` and `proof/proof-rs/crates/proof-schema/src/lib.rs` (`Provenance`); `sandbox/sandbox-rs/crates/sandbox-tee/src/energy.rs` (`AttestTrust`, `CounterClass`, `is_independently_rooted`); `jcp/jcp-rs/crates/jcp-efficiency/MAPPING.md` §4 and `src/training.rs` (`energy_attestation`). No mention of `sandbox-tee`, `CounterClass`, `AttestTrust`, `energy_attestation`, `PhysicallyRooted` or `IntegrityAttested` under `wai/` (grep) | **WAI's side closed at link 08 (PR #109); the family's side open.** energy-measurement §2 separates `CalibratedInstrument` (an external instrument with a current calibration) from `OnChipCounter` (a counter the measured silicon keeps, or any instrument whose uncertainty is not traceable). Each carries a declared uncertainty (§2.3); the counter carries its filtering state (§2.4), the instrument a calibration reference (§2.5). `HwShunt` is legacy, accepted and never emitted (§2.6), and IOReport is `OnChipCounter` (§2.9). `energy_class.rs` is the one definition, and the reference meters seal `OnChipCounter`. The "reused verbatim" sentence is gone; `video_receipt.rs` and `energy_binding.rs` still call the ladder the family's, and §2 says the three-name vocabulary is extended, not replaced, with both refinements mapping to `HwShunt`. §2.9's list of differing documents is partial (§5.3). No WAI text references `sandbox-tee`'s ladder (grep). The family's IOReport split and whether its receipt-level ladder should separate the two kinds of hardware evidence are unassigned (below) |
|
||||
| G5 | **The codec registry has no AV2, VVC, HEVC, LCEVC, MPEG-H Audio, AVS3 or Audio Vivid capability.** Brazil mandates three of these (§3.3). Two are Chinese standards: AVS3 (a group standard, with its national standard pending) and Audio Vivid (GB/T 46271-2025). 2030 §6.1 (*Honest scope*) records that WAI registers no VSEI-family codec. **The tree's codec survey is a second surface to reconcile.** `wai/glossary/codecs.json`, which `wai/README.md` describes as the "Reference catalog of media codecs surveyed for capability registration", has entries for HEVC / H.265 and VVC / H.266 (both `niche`), LCEVC (`niche`) and ECM (`academic`, JVET's exploration model beyond VVC), and none for AV2, MPEG-H Audio, AVS3 or Audio Vivid. `glossary/SCHEMA.md` defines `success` as "carries non-trivial production traffic today" and `niche` as "alive in specific contexts". §4 reports HEVC in production use at 65% in a vendor survey (**S** ⚠), §3.3 reports VVC as a national broadcast system's main codec (**R** ⚠), and the HEVC entry's own description calls it universal in broadcast UHD. The grades are descriptive and the contrary evidence is unverified, so this is a reconciliation item, not a correction. | capability strings in `wai-rs/src/codecs/*.rs`: none for these codecs. Other mentions, from a grep over `*.rs`, `*.md`, `*.json`, `*.toml`, `*.yml`, `*.yaml`, `*.html`, `*.ts` and `*.js` under `wai/`: the `glossary/codecs.json` entries above; the H.264/H.265 royalty sentence in `SPEC.md` and `CHANGELOG.md`; the BD-rate note that names VVC in `wai-rs/src/codecs/mod.rs` and `wai-rs/src/neural/mlicpp.rs`; `HEVC` as an example name in `glossary/SCHEMA.md`; the 2030 landscape. Royalty class per codec **not established by this sweep**: only AV2's royalty-free patent policy was seen at the issuing body (the AOMedia press release, §2.4) | **Closed in the registry at link 04 (PR #105); the glossary is not reconciled.** SPEC §5 *Broadcast and streaming codecs* registers `wai.video.{av2,vvc,hevc,avc,vp9,evc,evc.baseline,avs3,lcevc}` and `wai.audio.{mpegh,avs3,aac}`, each with one payload framing and a licence class whose basis is recorded in [`media-codec-registry.md`](media-codec-registry.md). `wai.audio.avs3` names AVS3 audio / Audio Vivid (GB/T 46271-2025). All are sink-supplied, none is a default, and `wai.video.lcevc` is companion-only. SPEC §7 classes AVC, HEVC, VVC and VP9 `StandardDefined` on the conformance clauses read, AV2 `StandardTolerance`, and EVC, AVS3, LCEVC, MPEG-H 3D Audio and AAC `StandardUnclassified`. The licence classes rest on the registry's recorded bases, not on this sweep. `glossary/codecs.json` is unchanged, so its grades still stand against §3.3 and §4 |
|
||||
| G6 | **No WAI receipt says who can check the energy figure, and the base JWP receipt does not carry even the acquisition class that `energy-measurement.md` requires.** The precedents in §5.1 differ exactly on who can check: instrument-signed (OCMF), peer-reviewed but unsigned, log-registered (SCITT), TEE-attested with a derived trust level (the family's `sandbox-tee` `AttestTrust`), or producer-asserted. **The base JWP receipt** has no acquisition-class field; its group signature covers `total_joules_micro` and no class, and the per-object `joules_micro` is in neither the Merkle leaf nor the signature. `energy-measurement.md` §2 requires a class inside the signature for any non-zero figure, while its header says it adds "no new wire format". **The video receipt** signs its class. **The energy binding** carries its class in a C2PA companion assertion signed as part of whatever manifest carries it. **The OER receipt** carries an evidential grade and a ledger identity, and no class. 2030 §8 item 4 queues typed receipt classes. | `extensions/energy-measurement.md` header, §1, §2 and §5; `extensions/jwp-receipts.md` §2.1–§2.4; `joule-code/joule-code-rs/crates/joule-moq/src/receipt.rs` (`ObjectReceipt`, `GroupReceipt::signing_payload`: no class); `wai-rs/src/energy_binding.rs`, `wai-rs/src/video_receipt.rs`; `extensions/oer-receipts.md` §2–§3, `wai-rs/src/oer.rs`; no reference to the family ladder under `wai/` (grep, as in G4) | **Partly closed at link 10 (PR #112); the rest open.** Every receipt within energy-measurement's scope that `wai-rs` and `wai-quantum` seal can carry the class (link 08, PR #109). jwp-receipts §2.4.3–§2.4.7 add a `claim` kind with typed classes. An `energy` claim (§2.4.5) restates a JWP object's figure beside its class, the class's evidence, a coverage statement and a grade; a verifier holding the object receipt rejects the pair if the figures differ, and one holding the OER receipt the claim names checks that grade and figure agree. `claim_receipt.rs` (feature `receipt_claims`) implements it, with vectors in `claim-conformance/`. Open, as energy-measurement §1 records: the JWP object receipt has no member of its own for the class; the group total has none, and nothing carries one for it; the `WAI_RECEIPT` property's class and evidence sit outside any signature; an OER/2 receipt has no member for the class. On who can check: a claim carries its signer's authority and no more (§2.4.4); key discovery and an authority class for who may sign which class are not done (CHANGELOG, v0.4.0 entry for typed receipt classes); no WAI receipt references `sandbox-tee`'s ladder or `efficiency/1`'s attestation (grep). *Status after the v0.4.0 change "Energy reports, labelled object leaves, and what the signatures cover":* the object's class rides in its labelled leaf, a group total can carry a signed class of its own, and a `WAI_RECEIPT` property's class counts only where a signed statement restates it; joule-moq verifies both labelled forms. *Status after the v0.4.0 change "Key discovery, signing authority and module revocation":* key discovery and an authority class for who may sign which class are jwp-receipts §7 (`wai-rs` `key_log`, feature `key_discovery`; `signer-conformance/`); a hardware-class energy authority is confirmed only under a pinned anchor, and a verifier reports whether the key also holds `delivery` |
|
||||
| G7 | **Explicit parameter transmission appears in `determinism-tiers.md` only as a generic route. Its first published cross-NPU instance (arXiv 2606.28027) and its cost are not recorded.** Also unrecorded: the mixed-precision entropy-synchronisation line (arXiv 2608.00483: FP16/FP32 across GPUs, no bit-exactness claim); the quantised learned-coding line (arXiv 2007.04684, 2111.09348); and IEEE 1857.11's use of *reproducibility cross-checks* rather than bit-exact decode (**R** ⚠), a reproducibility notion the tier vocabulary does not name. Both 2606.28027 and 2608.00483 target a synchronised entropy stage, so at most `entropy-consistency`, and 2608.00483 only over the GPUs it tested. On `main`, whether such a decode may back `replicate` depends on which of G10's conditions is read. | `extensions/determinism-tiers.md` §1–§3; `SPEC.md` §7 (read) | **Partly closed at link 02 (PR #103).** determinism-tiers §1 names arXiv 2606.28027 as the explicit-parameter instance and says the side information costs bitrate (no magnitude), and names a quantised decoder, arXiv 2312.11209, as the route to a reproducible reconstruction. Such a decode reaches `entropy-consistency` and does not back `replicate` (G10). Not in the tree at link 10 (grep): arXiv 2608.00483, the quantised line 2007.04684 / 2111.09348, and IEEE 1857.11's reproducibility cross-checks; the last is described in [`syfai-input.md`](syfai-input.md) §5, committed with this record |
|
||||
| G7 | **Explicit parameter transmission appears in `determinism-tiers.md` only as a generic route. Its first published cross-NPU instance (arXiv 2606.28027) and its cost are not recorded.** Also unrecorded: the mixed-precision entropy-synchronisation line (arXiv 2608.00483: FP16/FP32 across GPUs, no bit-exactness claim); the quantised learned-coding line (arXiv 2007.04684, 2111.09348); and IEEE 1857.11's use of *reproducibility cross-checks* rather than bit-exact decode (**R** ⚠), a reproducibility notion the tier vocabulary does not name. Both 2606.28027 and 2608.00483 target a synchronised entropy stage, so at most `entropy-consistency`, and 2608.00483 only over the GPUs it tested. On `main`, whether such a decode may back `replicate` depends on which of G10's conditions is read. *Corrected 2026-11-03, body read:* arXiv 2608.00483 makes no bit-exactness claim in its abstract, though its body claims bit-exact hashes within a GPU generation (§III-B; its row above). | `extensions/determinism-tiers.md` §1–§3; `SPEC.md` §7 (read) | **Partly closed at link 02 (PR #103).** determinism-tiers §1 names arXiv 2606.28027 as the explicit-parameter instance and says the side information costs bitrate (no magnitude), and names a quantised decoder, arXiv 2312.11209, as the route to a reproducible reconstruction. Such a decode reaches `entropy-consistency` and does not back `replicate` (G10). Not in the tree at link 10 (grep): arXiv 2608.00483, the quantised line 2007.04684 / 2111.09348, and IEEE 1857.11's reproducibility cross-checks; the last is described in [`syfai-input.md`](syfai-input.md) §5, committed with this record. *Since 2026-11-03:* determinism-tiers §1 records arXiv 2608.00483 as float synchronised on a tested hardware class, which reaches no tier (its Table IV and §III-B), and the integer routes of arXiv 2502.20762 (§4.5) and of an integer-trained codec (OpenReview `KCQo0fXtFH`, abstract); the quantised line 2007.04684 / 2111.09348 is still not in the tree |
|
||||
| G8 | **Standards inputs not tracked in the tree:** SyfAI; 3GPP TR 26.942; DVB S100 and CM-EE; the beyond-VVC CfP; Gaussian Splat Coding Exploration 45; IEEE 1857.11 / P1857.11.1; UWA 042; India D2M; the JPEG AI core experiments on energy consumption and bit-exact reconstruction. | a case-insensitive, word-bounded `git grep -P` over tracked `*.md`, `*.rs`, `*.json`, `*.toml`, `*.yml`, `*.yaml`, `*.html`, `*.ts` and `*.js` files finds none of these designations outside this record. The one other hit for `uwa` is the Hausa word in `aac/aac-rs/crates/aac-core/src/translations_data.json` (`"ha":"uwa"`), which is not the organisation. MPEG's splat work *is* tracked, though not Exploration 45 by name: `media-codec-registry.md` and `codecs/mod.rs` register `wai.splat.sog` against "MPEG WG4" work in progress, `3d-neural-coding-landscape.md` records splat coding under standardisation, and 2030 §6.4 records an NNC amendment covering Gaussian splats (G11) | **Partly closed by the two notes committed with this record.** [`syfai-input.md`](syfai-input.md) tracks SyfAI, IEEE 1857.11 and UWA 042; [`energy-exposure-mapping.md`](energy-exposure-mapping.md) tracks TR 26.942, DVB S100 and CM-EE. The same grep over the tree at link 10 finds the beyond-VVC CfP, Exploration 45, India D2M and the JPEG AI core experiments in no document but this record. Those four stay open |
|
||||
| G9 | **WAI's texts and code class `wai.image.jpegai` in ways that contradict one another, and SPEC contradicts itself on it.** SPEC §5, the capability's registration (`SPEC.md`:197), describes the decode as "integer entropy front end (mANS) + integer dequant/value path + fixed-order synthesis and post-filters" and declares "**decode-equivalence**: one pinned reconstruction … reproducible on every machine (a portable decoded-pixel hash)". `reconstruction-binding.md` §1 (:30–31) says the decode to RGB is "byte-identical on every machine", and §2 (:72) sets the binding's criterion at `decode-equivalence`; `determinism-tiers.md` §2–§3 list the capability there. SPEC §7 lets a neural capability back `replicate` only "with no float operation anywhere" (:445–448). `capability_replicate_class` returns `StandardDefined` for it through the default arm, and SPEC §7 defines that class as "reconstruction is fixed by the named codec/standard … the neural float hazard does not apply"; neither half holds for JPEG AI (§2.2; its decode runs the model). The strict `replicate` check reads only the envelope's prior (`neural/mod.rs`:282–292). **Loose ends:** (i) `jpegai_synthesis.rs` and `jpegai_mans.rs` say they back `wai.neural.jpeg_ai`, a string registered nowhere; (ii) the decode has f32 steps between its integer stages — its dequantisation among them, although SPEC §5 calls it integer; (iii) the byte-for-byte claim is made normatively, in SPEC §5 and `reconstruction-binding.md` §1, and rests on an `exp()` from the platform maths library and on no measurement on a second platform; (iv) the reference sink's dispatch (`neural/mod.rs`:205–216) uses the exact decoder only when the integer weight bundle is present, and otherwise falls back to a float ONNX Runtime decode. | `SPEC.md` §5 (:197) and §7 (:445–448, the `StandardDefined` bullet at :476–478, :492–495); `extensions/reconstruction-binding.md` §1 (:21–35) and §2 (:72); `extensions/determinism-tiers.md` §2–§3; `wai-rs/src/codecs/mod.rs` (the default arm and the test asserting `StandardDefined`); `wai-rs/src/jpegai_decode.rs`, `jpegai_synthesis.rs`, `jpegai_mans.rs`, `jpegai_pipeline.rs`; `wai-rs/src/neural/mod.rs`:205–216, :282–292; `git grep -n decode-equivalence` over `wai/` | **Closed at link 02 (PR #103), with the maths library removed at link 03 (PR #104).** SPEC §5's row registers `entropy-consistency` (registry class `NeuralEntropyExact`), describes dequantisation, latent refinement, post-filters and colour conversion as IEEE-754 float with a fixed evaluation order, and says "**Downgraded in v0.4.0:** this row previously claimed decode-equivalence". `reconstruction-binding.md` §1–§2 make the pinned reconstruction a property of the exact decoder, reproducible under that decoder's build and floating-point conditions. SPEC §7 states the new class and the reason. Loose ends: (i) fixed — the module docs name `wai.image.jpegai`, and `wai.neural.jpeg_ai` appears nowhere else in the tree (grep); (ii) the float steps remain and are now stated as float; (iii) link 03 builds `exp` and rounding from IEEE-754 basic operations (`float_repro`), guards the decode path with clippy's `disallowed_methods` and a linked-symbol check (`tests/jpegai_decode_symbols.rs`), and pins nine hashes for one codestream; determinism-tiers §6 records them identical on macOS arm64, wasm32-wasip2, Linux arm64 and Linux x86_64 under user-mode emulation, and not measured on Windows, macOS x86_64 or x86_64 hardware; (iv) the float fallback remains, and SPEC §5, determinism-tiers §5 and reconstruction-binding §1 now state it, and that the result does not record which path ran. Still true: a `norm.json` with a missing or mistyped key panics in the loaders instead of falling back, as the `try_jpegai_exact` doc comment says |
|
||||
| G9 | **WAI's texts and code class `wai.image.jpegai` in ways that contradict one another, and SPEC contradicts itself on it.** SPEC §5, the capability's registration (`SPEC.md`:197), describes the decode as "integer entropy front end (mANS) + integer dequant/value path + fixed-order synthesis and post-filters" and declares "**decode-equivalence**: one pinned reconstruction … reproducible on every machine (a portable decoded-pixel hash)". `reconstruction-binding.md` §1 (:30–31) says the decode to RGB is "byte-identical on every machine", and §2 (:72) sets the binding's criterion at `decode-equivalence`; `determinism-tiers.md` §2–§3 list the capability there. SPEC §7 lets a neural capability back `replicate` only "with no float operation anywhere" (:445–448). `capability_replicate_class` returns `StandardDefined` for it through the default arm, and SPEC §7 defines that class as "reconstruction is fixed by the named codec/standard … the neural float hazard does not apply"; neither half holds for JPEG AI (§2.2; its decode runs the model). The strict `replicate` check reads only the envelope's prior (`neural/mod.rs`:282–292). **Loose ends:** (i) `jpegai_synthesis.rs` and `jpegai_mans.rs` say they back `wai.neural.jpeg_ai`, a string registered nowhere; (ii) the decode has f32 steps between its integer stages — its dequantisation among them, although SPEC §5 calls it integer; (iii) the byte-for-byte claim is made normatively, in SPEC §5 and `reconstruction-binding.md` §1, and rests on an `exp()` from the platform maths library and on no measurement on a second platform; (iv) the reference sink's dispatch (`neural/mod.rs`:205–216) uses the exact decoder only when the integer weight bundle is present, and otherwise falls back to a float ONNX Runtime decode. | `SPEC.md` §5 (:197) and §7 (:445–448, the `StandardDefined` bullet at :476–478, :492–495); `extensions/reconstruction-binding.md` §1 (:21–35) and §2 (:72); `extensions/determinism-tiers.md` §2–§3; `wai-rs/src/codecs/mod.rs` (the default arm and the test asserting `StandardDefined`); `wai-rs/src/jpegai_decode.rs`, `jpegai_synthesis.rs`, `jpegai_mans.rs`, `jpegai_pipeline.rs`; `wai-rs/src/neural/mod.rs`:205–216, :282–292; `git grep -n decode-equivalence` over `wai/` | **Closed at link 02 (PR #103), with the maths library removed at link 03 (PR #104).** SPEC §5's row registers `entropy-consistency` (registry class `NeuralEntropyExact`), describes dequantisation, latent refinement, post-filters and colour conversion as IEEE-754 float with a fixed evaluation order, and says "**Downgraded in v0.4.0:** this row previously claimed decode-equivalence". `reconstruction-binding.md` §1–§2 make the pinned reconstruction a property of the exact decoder, reproducible under that decoder's build and floating-point conditions. SPEC §7 states the new class and the reason. Loose ends: (i) fixed — the module docs name `wai.image.jpegai`, and `wai.neural.jpeg_ai` appears nowhere else in the tree (grep); (ii) the float steps remain and are now stated as float; (iii) link 03 builds `exp` and rounding from IEEE-754 basic operations (`float_repro`), guards the decode path with clippy's `disallowed_methods` and a linked-symbol check (`tests/jpegai_decode_symbols.rs`), and pins nine hashes for one codestream; determinism-tiers §6 records them identical on macOS arm64, wasm32-wasip2, Linux arm64 and Linux x86_64 under user-mode emulation, and not measured on Windows, macOS x86_64 or x86_64 hardware; (iv) the float fallback remains, and SPEC §5, determinism-tiers §5 and reconstruction-binding §1 now state it, and that the result does not record which path ran. Still true: a `norm.json` with a missing or mistyped key panics in the loaders instead of falling back, as the `try_jpegai_exact` doc comment says. *Since 2026-11-04:* the result records which path ran, against (iv): the dispatch returns it (`neural::Dispatched::path`, determinism-tiers §5), a session claim's decoder entry states it as its `variant` (jwp-receipts §2.4.12), and the reference check of a reconstruction binding reads it (reconstruction-binding §2) |
|
||||
| G10 | **WAI's texts give three conditions for a neural decode to back `replicate` or a portable reconstruction hash, and SPEC states all three.** (1) **All-integer:** SPEC §7 (`SPEC.md`:445–448, :492–495), enforced in code on the envelope's declared prior (`replicate_is_sound` requires `DeterminismClass::IntegerExact`, a substring heuristic over `Prior.determinism`). (2) **`decode-equivalence` with fixed-order float admitted:** `determinism-tiers.md` §2 defines the tier over "integer / fixed-order arithmetic end to end" (:57–58), and SPEC §5 registers `wai.image.jpegai`, whose float stages run in a fixed order, at that tier. (3) **`entropy-consistency` as the floor:** SPEC §7's closing paragraph (:497–505) calls it "the floor for a sound neural `replicate`", and `determinism-tiers.md` §2–§3 repeat it; §3 also says "a float neural decode meets neither tier", which does not square with the explicit-parameter route. A decode with integer entropy and fixed-order float synthesis meets (2) and (3) but not (1). | `SPEC.md` §5 (:197) and §7 (:445–448, :492–495, :497–505); `extensions/determinism-tiers.md` §1–§3; `extensions/reconstruction-binding.md` §1–§2; `wai-rs/src/container.rs` (`Prior::determinism_class` at :108–117, `is_cross_hardware_exact`, `replicate_is_sound`) | **Closed at link 02 (PR #103), on condition (1).** SPEC §7: a neural capability backs `replicate` only if its entire decode path is integer / fixed-point with no float operation, "the only construction by which a neural capability is registered at `decode-equivalence`"; `entropy-consistency` ("necessary for a sound neural `replicate` … and not sufficient") and a reconstruction pinned by one implementation's fixed-order float stages MUST NOT back it. determinism-tiers §2 defines `decode-equivalence` over "integer / fixed-point arithmetic end to end", and §3 says a decode that derives its entropy parameters in float meets neither tier while one that reads them from the codestream, or derives them in integer arithmetic, and runs later stages in float meets `entropy-consistency` only. The strict check reads the registry class before the prior. Remaining: the prior check still reads a sender-declared string by substring, which determinism-tiers §3 states |
|
||||
| G11 | **Three volumetric registrations sit against MPEG's 2026 splat and point-cloud work, and none of the sweep's rows was mapped to them.** `wai.splat.sog` is a candidate "registered so a capability exists ahead of an MPEG freeze" (registry: "MPEG WG4 WIP", "RF (open work)"). `wai.pc.gpcc` (23090-9) and `wai.pc.vpcc` (23090-5) are registered POOL. None has a decoder inside `wai-rs`; the registry records demonstrated sink-side decodes via `tools/wai_carriage_demo.sh` (not re-run here, and not in CI). The sweep found a V-PCC Gaussian-splat CDAM (MPEG 153) *(2026-09-30: at Draft Amendment since MPEG 154, WG 4 N 837)*, a G-PCC Gaussian-splat Amd 1 at CDAM (MPEG 154), Enhanced G-PCC ISO/IEC 23090-38 at FDIS (MPEG 155), and Exploration 45's document series up to a call for content at MPEG 155, all P✓; its tracks and dates are **R**. Questions: **(a)** whether what MPEG standardises is what `wai.splat.sog` names, which cites Self-Organizing Gaussians / SOGS while the part page names no candidate technology; **(b)** whether its royalty class holds (`capability_license` returns `RoyaltyFree` through the default arm; nothing the sweep read establishes the licensing of a codec MPEG selects); **(c)** whether 23090-38 is a payload `wai.pc.gpcc` admits or needs its own capability, and whether the splat amendments change what the two PCC capabilities admit; **(d)** the replicate class: all three fall through to `StandardDefined`, although `wai.splat.sog` has no frozen standard. The tree's "WG4" attribution was not checked (the Exploration 45 page names no working group), and `3d-neural-coding-landscape.md` describes a "future `wai.pointcloud.*`" without mentioning the registered `wai.pc.*` strings. | `research/media-codec-registry.md`; `wai-rs/src/codecs/mod.rs` (`CAP_PC_GPCC`, `CAP_PC_VPCC`, `CAP_SPLAT_SOG` and their arms); `tools/wai_carriage_demo.sh`; `wai-rs/tests/carriage.rs`; `research/3d-neural-coding-landscape.md`; 2030 §6.4 | **(d) closed at link 02 (PR #103); (b) made explicit at link 04 (PR #105); (a), (c), the "WG4" attribution and the `wai.pointcloud.*` sentence open.** All three are `StandardUnclassified`, the class for a standard whose decoder-conformance clauses have not been checked (SPEC §7 notes that `wai.splat.sog` names no frozen standard yet). `capability_license` lists `wai.splat.sog` explicitly as royalty-free ("SOG open work"), which makes the class explicit and does not establish it. *(2026-09-30: `capability_license` now classifies `wai.splat.sog` patent-pool, conservatively and provisionally while it is a candidate; SPEC §5, Licence classes)* `codecs/mod.rs` and `media-codec-registry.md` still say "MPEG WG4", and `3d-neural-coding-landscape.md` still describes a "future `wai.pointcloud.*`". Exploration 45 is untracked (G8) |
|
||||
|
||||
|
|
@ -863,6 +863,9 @@ conclusion is also exposed to the unreached areas in §1.
|
|||
PR #103) the consequence for `replicate` is settled either way: a decode that
|
||||
is not integer / fixed-point end to end does not back it, whatever its
|
||||
reconstructions do on the devices tested.
|
||||
*Corrected 2026-11-03, body read:* arXiv 2608.00483 synchronises the entropy
|
||||
stage by mixed precision, and the identical output it reports holds only
|
||||
within the groups of GPUs its tests found (Table IV).
|
||||
- **F6. "SyfAI's AI update-framework thread covers the properties WAI's `Prior`
|
||||
and determinism tiers specify" (§2.1, G3).** This rests on a **secondary**
|
||||
report. It is falsified or narrowed if either of the following scopes bit
|
||||
|
|
|
|||
326
wai/research/catalogue-prior.md
Normal file
326
wai/research/catalogue-prior.md
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
# What a linear catalogue prior costs, measured on a studio's open films
|
||||
|
||||
> **Status.** A measured record and its procedure, informative. Every measured figure below
|
||||
> is computed from the readings in [`catalogue-prior/`](catalogue-prior/), and
|
||||
> `tools/wai_catalogue_prior_verify.py`, which shares no code with the scripts that took
|
||||
> them, recomputes each from those readings and checks every figure this page quotes (each
|
||||
> is followed by a `cat` marker naming the value it is checked against). The readings were
|
||||
> taken on 4 November 2026 (UTC). No WAI capability is measured here: the record decides
|
||||
> whether a catalogue basis should become one, and §5 says it should not.
|
||||
|
||||
## 1. The question, and what is measured
|
||||
|
||||
A catalogue prior is a prior learned from a catalogue of titles and held by each sink, so
|
||||
that a title carries only what the prior does not predict (SPEC §3.1, the shared ambient
|
||||
prior). WAI already carries such a prior: pinned by its SHA-256, served by digest, and
|
||||
refused by name when a sink does not hold it (SPEC §3.1, [prior-carriage](../extensions/prior-carriage.md)).
|
||||
The claim splits into three, each of which can be refuted on its own (run.json, `rule`):
|
||||
|
||||
- **H1a, a linear or spectral basis.** A basis learned on the catalogue (a mean frame, or
|
||||
the principal components of its frames) captures a held-out title better than a generic
|
||||
transform, and in front of a codec it saves bytes (§3.3, §3.5).
|
||||
- **H1b, exact recurrence.** Frames recur across titles, byte for byte or within a tight
|
||||
bound, so a sink that holds them once never fetches them again (§3.4).
|
||||
- **H1c, a learned prior.** A neural codec's parameter set tuned on the catalogue saves
|
||||
bytes against its generic set on a held-out title. Nothing here measures it (§6).
|
||||
|
||||
A catalogue is a set of whole titles under one rights holder, each held out whole when it
|
||||
is tested (§3.1). This record uses openly licensed films of one studio: they share a
|
||||
production pipeline and a house style, but no characters or sets (§3.1). A negative result
|
||||
on such a catalogue refutes H1a and H1b for a studio's catalogue, not for a franchise, and
|
||||
says nothing of H1c (§6).
|
||||
|
||||
The prior's bytes are charged once per sink, not once per catalogue, because the sink
|
||||
reconstructs and so every sink that decodes must hold it (SPEC §3.1). The break-even is the
|
||||
prior's bytes divided by the mean saving per title, in titles watched per sink (run.json,
|
||||
`rule.go`).
|
||||
|
||||
## 2. The rule, and when it was fixed
|
||||
|
||||
The rule below was written at 2026-11-04T01:58Z, before any result of measurements (a) or
|
||||
(c) had been seen (run.json, `timeline`). That is attested, not proved: no copy of the rule
|
||||
was committed anywhere before the results existed. The record is consistent with it
|
||||
(run.json, `timeline`):
|
||||
|
||||
1. The rate proxy's four target PSNRs were recorded with the basis script at 01:43:38Z.
|
||||
2. Moment accumulation finished at 01:49:04Z, and printed no figure.
|
||||
3. The residual-coding script was last changed at 01:58:51Z.
|
||||
4. The first result file, `a_basis.json`, was written at 02:00:43Z. The basis script's only
|
||||
later change, at 02:01:24Z, is a print format.
|
||||
|
||||
The rule (run.json, `rule`):
|
||||
|
||||
- **GO**, worth a capability and a demonstration, needs both: a BD-rate of −10 % or less
|
||||
against per-title AV1 on both PSNR and VMAF, with the side information charged, on at
|
||||
least three of the four held-out titles; and a break-even of at most five titles per
|
||||
sink, with the prior's bytes counted.
|
||||
- **K1 (H1a)** holds when the catalogue basis's rate proxy is not at least 5 % below a
|
||||
generic DCT's at both 35 dB and 40 dB on the median title.
|
||||
- **K2 (H1a)** holds when the catalogue-basis residual and its coefficients cost more bytes
|
||||
than AV1 alone (a BD-rate above 0) on both metrics on the median title.
|
||||
- **K3 (H1b)** holds when non-flat material recurring across titles within a tight bound
|
||||
(a thumbnail PSNR of 40 dB or more and a full-size PSNR of 35 dB or more) is under 1 % of
|
||||
each title's runtime.
|
||||
- Anything between GO and a kill is inconclusive.
|
||||
|
||||
### 2.1 Faults in the rule, found after the run
|
||||
|
||||
Three faults were found after the results existed. None changes this run's verdicts, which
|
||||
rest on K2 and K3 (§5):
|
||||
|
||||
1. No clause can kill H1c: K1 and K2 concern a linear basis, and K3 recurrence.
|
||||
2. GO and K1 can both hold: K1 judges an intra rate proxy with no prediction, and GO a
|
||||
real codec. Here K2 holds as well, so the kill does not rest on K1 alone (§5).
|
||||
3. The per-title AV1 bar was run through a non-standard 10-bit pipeline, `base10`, which is
|
||||
less efficient than plain 8-bit AV1 (§4.3). A −10 % GO against `base10` is
|
||||
−3.1 %<!--cat:go_vs_base8_pct--> against plain AV1 (§4.3). Plain per-title AV1 is the stricter bar, and every residual arm is
|
||||
further from it (§4.3).
|
||||
|
||||
## 3. Procedure
|
||||
|
||||
### 3.1 Corpus
|
||||
|
||||
Openly licensed films of one studio, each named in run.json by its file's size and
|
||||
SHA-256, with the SHA-256 of the licence page fetched for it (run.json, `corpus`):
|
||||
|
||||
| code | title (year) | licence, as its page states it | file SHA-256 |
|
||||
|---|---|---|---|
|
||||
| ED | Elephants Dream (2006) | Creative Commons, version not stated on the page fetched | `6fc295e0…f7558a547` |
|
||||
| BBB | Big Buck Bunny (2008) | Creative Commons Attribution 3.0 | `b0c9ade8…f88b33b67` (the archive) |
|
||||
| SIN | Sintel (2010) | Creative Commons Attribution 3.0 | `fae11ed4…19c3d9920` |
|
||||
| TOS | Tears of Steel (2012) | Creative Commons Attribution 3.0 | `99486359…e185987a15` |
|
||||
|
||||
Another short film of the same studio was left out because no licence statement could be
|
||||
fetched for it (§6). The films' frames are not in this repository; only figures derived
|
||||
from them are (§3.2).
|
||||
|
||||
### 3.2 Analysis raster
|
||||
|
||||
Each title was decoded to luma at 640×360 by area scaling, kept as decoded: 8-bit, limited
|
||||
range (run.json, `analysis_raster`). The two wide films were stretched to 16:9, since no
|
||||
title has letterbox bars (§6). From that raster: 128×72 by a 5×5 area mean, in float, and
|
||||
64×36 by a 10×10 area mean, rounded to 8 bits (run.json, `analysis_raster`).
|
||||
|
||||
### 3.3 Measurement (a): does a catalogue basis capture a held-out title better than a generic transform?
|
||||
|
||||
- **Split.** Each title is cut in time into a first half A and a second half B; the test
|
||||
set is half B of the held-out title (`a_basis.json`).
|
||||
- **Bases.** The catalogue basis is the principal components (the Karhunen–Loève transform)
|
||||
of the other titles, leave one title out; the per-title basis is that of half A of
|
||||
the held-out title; the generic basis is an orthonormal 2-D DCT-II whose coefficients are
|
||||
ordered by the catalogue's training statistics (`a_basis.json`). Each vector has its own
|
||||
mean removed first, and the principal-component bases also remove their training mean,
|
||||
the catalogue's "central mean" (`a_basis.json`).
|
||||
- **Granularities.** Whole frames at 128×72; 8×8 and 16×16 blocks at 640×360, every second
|
||||
frame; and 8×8 blocks of the difference of consecutive frames, an un-compensated stand-in
|
||||
for inter-frame residuals (`a_basis.json`).
|
||||
- **Rate proxy.** The Gaussian reverse water-filling rate of the test coefficients' second
|
||||
moments, at the mean squared error of a target PSNR: the classical yardstick for ranking
|
||||
transforms, as an ideal intra coder with no prediction (`a_basis.json`). It ranks
|
||||
transforms; its bits per pixel are not a codec's (§6). Every score comes from first and
|
||||
second moments over every test vector, so nothing is sampled (`a_basis.json`).
|
||||
|
||||
### 3.4 Measurement (b): what recurs across titles?
|
||||
|
||||
- **Thumbnails.** 64×36, 8-bit. A frame is flat when its thumbnail's standard deviation is
|
||||
under 2 code values, as black frames and fades to one tone are (`b_recur.json`).
|
||||
- **Recurrence.** Identical thumbnail bytes in another title (exact), or the nearest
|
||||
non-flat frame of another title at a thumbnail PSNR of 35 dB or 40 dB, then measured again
|
||||
on the two frames at 640×360 (`b_recur.json`). The nearest frame of the same title at
|
||||
least 10 s away is measured the same way: a key frame every 10 s puts it out of the
|
||||
encoder's reach in this run (`d_exact.json`).
|
||||
- **Bytes.** Each whole title was encoded once with SVT-AV1, preset 8, CRF 36, a key frame
|
||||
every 240 frames and `tune=1`, at 640×360 luma, and each temporal unit's bytes were
|
||||
charged to the frame it shows (`d_exact.json`). The attribution is approximate, since
|
||||
hidden reference data rides in the unit that carries it (§6).
|
||||
|
||||
### 3.5 Measurement (c): bytes at matched quality, residual coding against per-title AV1
|
||||
|
||||
- **The clip.** For each title, 1,440 frames (60 s) starting at 60 % of the title, inside
|
||||
half B, so neither basis saw it (`c_rd.json`).
|
||||
- **The arms** (`c_rd.json`):
|
||||
- `base8`: AV1, 8-bit, on the clip.
|
||||
- `base10`: AV1, 10-bit, on 2*x* + 256, the residual arms' own pipeline and scale.
|
||||
- `catK`: AV1, 10-bit, on 2(*x* − *x̂*) + 512, where *x̂* is the bilinear ×5 upsampling of
|
||||
the frame's DC, the catalogue mean and *K* catalogue components at 128×72. `cat0` is the
|
||||
mean alone; `cat64` and `cat256` add 64 and 256 components. The coefficients are
|
||||
quantised with a step of 2 (the DC with a step of 0.25) and charged at the larger of
|
||||
two estimates of their bytes: their per-index empirical entropy over frame-to-frame
|
||||
differences, and a signed Exp-Golomb code. The reconstruction is round(*x̂* + (*y* − 512)/2).
|
||||
- `self64`: as `cat64`, with the per-title basis of half A.
|
||||
- **Coding.** SVT-AV1 v4.2.0 through FFmpeg, preset 8, CRF 20, 28, 36, 44 and 52, a key
|
||||
frame every 240 frames, `tune=1`; decoded by dav1d; bytes are the sum of the IVF frame
|
||||
payloads (`c_rd.json`).
|
||||
- **Quality.** Luma PSNR over the clip's pooled squared error, and VMAF (libvmaf, model
|
||||
`vmaf_v0.6.1`, mean pooling) on the same luma (`c_rd.json`). VMAF is not reproducible to
|
||||
the bit across builds, so it is a second check, and every verdict below holds on PSNR
|
||||
alone (§5).
|
||||
- **BD-rate.** The Bjøntegaard delta rate: the natural log of bytes fitted as a
|
||||
least-squares cubic in quality for each arm, both fits integrated over the quality range
|
||||
the two arms share, and the mean difference turned into a percentage (`c_rd.json`,
|
||||
`bd_vs_base10`). A positive figure is the extra bytes an arm spends for the same quality.
|
||||
The verifier recomputes every figure the run stored, and also integrates a
|
||||
piecewise-linear interpolant of the same points (§4.3).
|
||||
|
||||
### 3.6 Host and tools
|
||||
|
||||
A shared Linux x86_64 build host, each step in a user scope with a 64 GB memory cap, a low
|
||||
CPU weight and `nice 10` (run.json, `host`). FFmpeg 9.0.2 with SVT-AV1 4.2.0, dav1d 1.5.4
|
||||
and libvmaf 3.2.1; Python 3.14.4 with NumPy 2.3.5 (run.json, `tools`). The measurement
|
||||
scripts are not part of this record; §3.3–§3.5 state what they did.
|
||||
|
||||
## 4. Results
|
||||
|
||||
### 4.1 Measurement (a)
|
||||
|
||||
The rate proxy of the catalogue basis against the DCT's, the median over the titles; a
|
||||
negative figure is fewer bits (`a_basis.json`, §3.3):
|
||||
|
||||
| granularity | at 35 dB | at 40 dB |
|
||||
|---|---|---|
|
||||
| whole frames, 128×72 | +18.1 %<!--cat:proxy.frame128x72.catalogue.35.median--> | +10.4 %<!--cat:proxy.frame128x72.catalogue.40.median--> |
|
||||
| 8×8 blocks | −0.6 %<!--cat:proxy.block8.catalogue.35.median--> | −0.3 %<!--cat:proxy.block8.catalogue.40.median--> |
|
||||
| 16×16 blocks | +0.2 %<!--cat:proxy.block16.catalogue.35.median--> | +0.1 %<!--cat:proxy.block16.catalogue.40.median--> |
|
||||
| 8×8 frame differences | −0.9 %<!--cat:proxy.block8_diff.catalogue.35.median--> | −0.6 %<!--cat:proxy.block8_diff.catalogue.40.median--> |
|
||||
|
||||
- Over whole frames, a basis learned on the title's own first half is further from the DCT
|
||||
still, by +38.7 %<!--cat:proxy.frame128x72.per_title.35.SIN--> to
|
||||
+55.7 %<!--cat:proxy.frame128x72.per_title.35.ED--> at 35 dB: a film's frame statistics
|
||||
are not stationary from its first half to its second (`a_basis.json`).
|
||||
- The share of a held-out title's detail energy that the catalogue mean alone captures is
|
||||
−3.05 %<!--cat:mean_frame.ED.captured_ac_pct--> on ED, so that subtracting it adds energy
|
||||
there, and 1.72 %<!--cat:mean_frame.SIN.captured_ac_pct--> to
|
||||
2.87 %<!--cat:mean_frame.TOS.captured_ac_pct--> on the other titles
|
||||
(`a_basis.json`).
|
||||
- Counting each frame's DC, the mean alone captures
|
||||
78.4 %<!--cat:mean_frame.captured_incl_dc_pct.min--> to
|
||||
92.5 %<!--cat:mean_frame.captured_incl_dc_pct.max--> of a title's energy, and sixteen
|
||||
components capture 89.8 %<!--cat:mean_frame.k16_incl_dc_pct.min--> to
|
||||
96.5 %<!--cat:mean_frame.k16_incl_dc_pct.max-->, as sixteen DCT components do, within
|
||||
0.3<!--cat:mean_frame.k16_vs_dct_points_max--> of a point on every title (`a_basis.json`). A test of the form "a given share of spectral mass in a given
|
||||
number of components" is met by any of these transforms, and so does not discriminate.
|
||||
- Over 8×8 and 16×16 blocks the catalogue basis and the DCT are within
|
||||
1.2 %<!--cat:proxy_block_title_abs_max--> of each other on every title (`a_basis.json`), as the DCT's closeness to the Karhunen–Loève transform for natural
|
||||
images predicts (Ahmed, Natarajan and Rao, IEEE Trans. Computers C-23(1), 1974).
|
||||
|
||||
### 4.2 Measurement (b)
|
||||
|
||||
Shares of each title's runtime and of its AV1 bytes at CRF 36 (`d_exact.json`,
|
||||
`b_recur.json`, §3.4):
|
||||
|
||||
| title | flat frames: runtime / bytes | non-flat, exact in another title | another title's frame at 35 dB, both sizes: runtime / bytes | its own frame 10 s or more away at 35 dB: runtime / bytes |
|
||||
|---|---|---|---|---|
|
||||
| ED | 1.45 %<!--cat:recur.ED.flat.runtime_pct--> / 0.211 %<!--cat:recur.ED.flat.bytes_pct--> | 0<!--cat:recur.ED.exact_nonflat--> | 0.083 %<!--cat:recur.ED.cross35.runtime_pct--> / 0.002 %<!--cat:recur.ED.cross35.bytes_pct--> | 0.070 %<!--cat:recur.ED.self_far35.runtime_pct--> / 0.002 %<!--cat:recur.ED.self_far35.bytes_pct--> |
|
||||
| BBB | 0.66 %<!--cat:recur.BBB.flat.runtime_pct--> / 0.649 %<!--cat:recur.BBB.flat.bytes_pct--> | 0<!--cat:recur.BBB.exact_nonflat--> | 0.035 %<!--cat:recur.BBB.cross35.runtime_pct--> / 0.001 %<!--cat:recur.BBB.cross35.bytes_pct--> | 0.049 %<!--cat:recur.BBB.self_far35.runtime_pct--> / 0.002 %<!--cat:recur.BBB.self_far35.bytes_pct--> |
|
||||
| SIN | 2.26 %<!--cat:recur.SIN.flat.runtime_pct--> / 0.173 %<!--cat:recur.SIN.flat.bytes_pct--> | 0<!--cat:recur.SIN.exact_nonflat--> | 0.169 %<!--cat:recur.SIN.cross35.runtime_pct--> / 0.010 %<!--cat:recur.SIN.cross35.bytes_pct--> | 1.253 %<!--cat:recur.SIN.self_far35.runtime_pct--> / 0.984 %<!--cat:recur.SIN.self_far35.bytes_pct--> |
|
||||
| TOS | 1.53 %<!--cat:recur.TOS.flat.runtime_pct--> / 0.278 %<!--cat:recur.TOS.flat.bytes_pct--> | 0<!--cat:recur.TOS.exact_nonflat--> | 0 %<!--cat:recur.TOS.cross35.runtime_pct--> / 0 %<!--cat:recur.TOS.cross35.bytes_pct--> | 0 %<!--cat:recur.TOS.self_far35.runtime_pct--> / 0 %<!--cat:recur.TOS.self_far35.bytes_pct--> |
|
||||
|
||||
- No non-flat frame of one title matches another title's at 40 dB at full size
|
||||
(`b_recur.json`, `cross_ge35.full_psnr_ge40`).
|
||||
- The frames that match another title's at 35 dB are dark, nearly flat end-credit frames,
|
||||
their thumbnails spreading a median of 2.6<!--cat:recur.cross35_spread.min--> to
|
||||
3.0<!--cat:recur.cross35_spread.max--> code values against
|
||||
30<!--cat:recur.ordinary_spread.min--> to 37<!--cat:recur.ordinary_spread.max--> for the
|
||||
titles' ordinary non-flat frames (`d_exact.json`, `thumb_std_median`).
|
||||
- The one recurrence of size is inside one title: SIN shows earlier shots again minutes
|
||||
later, 0.98 %<!--cat:recur.SIN.self_far35.bytes_pct--> of its bytes (`d_exact.json`,
|
||||
`self_far35`). It needs no catalogue.
|
||||
|
||||
### 4.3 Measurement (c)
|
||||
|
||||
The BD-rate of each arm against AV1 through the same 10-bit pipeline (`base10`), the median
|
||||
over the titles, PSNR / VMAF; GO needs −10 % or less on both (`c_rd.json`, §3.5):
|
||||
|
||||
| arm | median BD-rate, PSNR / VMAF | AV1's bytes only, PSNR / VMAF | against plain 8-bit AV1 (`base8`), PSNR |
|
||||
|---|---|---|---|
|
||||
| `cat0`, the mean alone | +2.1 %<!--cat:bd.cat0.base10.psnr.median--> / +2.4 %<!--cat:bd.cat0.base10.vmaf.median--> | +2.1 %<!--cat:bd_av1only.cat0.base10.psnr.median--> / +2.4 %<!--cat:bd_av1only.cat0.base10.vmaf.median--> | +10.4 %<!--cat:bd.cat0.base8.psnr.median--> |
|
||||
| `cat64` | +38.8 %<!--cat:bd.cat64.base10.psnr.median--> / +39.9 %<!--cat:bd.cat64.base10.vmaf.median--> | +31.8 %<!--cat:bd_av1only.cat64.base10.psnr.median--> / +29.6 %<!--cat:bd_av1only.cat64.base10.vmaf.median--> | +46.6 %<!--cat:bd.cat64.base8.psnr.median--> |
|
||||
| `cat256` | +77.7 %<!--cat:bd.cat256.base10.psnr.median--> / +80.5 %<!--cat:bd.cat256.base10.vmaf.median--> | +50.4 %<!--cat:bd_av1only.cat256.base10.psnr.median--> / +48.5 %<!--cat:bd_av1only.cat256.base10.vmaf.median--> | +80.3 %<!--cat:bd.cat256.base8.psnr.median--> |
|
||||
| `self64`, the title's own basis | +52.1 %<!--cat:bd.self64.base10.psnr.median--> / +52.0 %<!--cat:bd.self64.base10.vmaf.median--> | +44.9 %<!--cat:bd_av1only.self64.base10.psnr.median--> / +43.1 %<!--cat:bd_av1only.self64.base10.vmaf.median--> | +60.9 %<!--cat:bd.self64.base8.psnr.median--> |
|
||||
|
||||
- **The coefficients are not the main cost.** With their bytes left out, AV1 still spends
|
||||
more on the residual than on the picture: the middle column (`c_rd.json`, `av1_bytes`).
|
||||
The coefficients are 1.5 %<!--cat:side_pct.cat64.min--> to
|
||||
15.2 %<!--cat:side_pct.cat64.max--> of `cat64`'s bytes across the ladder (`c_rd.json`,
|
||||
`side_bytes`).
|
||||
- **The basis's own bytes**, at 16 bits per value, are 18,432 B<!--cat:basis_bytes.cat0-->
|
||||
for the mean alone, 1,198,080 B<!--cat:basis_bytes.cat64--> for 64 components and
|
||||
4,737,024 B<!--cat:basis_bytes.cat256--> for 256 (§3.5). Plain AV1 spends
|
||||
0.9 MB<!--cat:base8_mb.min--> to 11.6 MB<!--cat:base8_mb.max--> on one 60 s clip across
|
||||
the ladder (`c_rd.json`, `base8`). No arm saves bytes on PSNR on any title against either
|
||||
bar, so no number of titles watched recovers the basis's bytes, and there is no break-even
|
||||
(`c_rd.json`; the verifier's `no_psnr_saving_any_title`).
|
||||
- **The reconstruction from the basis alone** reaches 14.1 dB<!--cat:basis_only_psnr.cat0.min-->
|
||||
to 22.0 dB<!--cat:basis_only_psnr.cat64.max--> for the mean and for 64 components
|
||||
(`c_rd.json`, `basis_only_psnr`). The share of residual samples clipped was
|
||||
0<!--cat:clipped_max--> in every arm (`c_rd.json`, `clipped_frac`).
|
||||
- **The pipeline.** `base10` against `base8` costs a median of
|
||||
+7.7 %<!--cat:bd.base10.base8.psnr.median--> on PSNR and
|
||||
+1.5 %<!--cat:bd.base10.base8.vmaf.median--> on VMAF (`c_rd.json`): the ×2 scaling into
|
||||
10 bits is not the usual ×4, and it is less efficient than plain 8-bit AV1.
|
||||
- **The interpolation.** Integrated piecewise-linearly instead, no PSNR figure above moves
|
||||
by more than 0.48<!--cat:linear_shift_psnr_max--> of a point (the verifier's
|
||||
`bd_linear`). VMAF near saturation is not stable under the cubic fit: on TOS, `cat0`
|
||||
against `base8` gives −5.0 %<!--cat:bd.cat0.base8.vmaf.TOS--> by the cubic and
|
||||
+10.2 %<!--cat:bd_linear.cat0.base8.vmaf.TOS--> by the linear interpolant, and `base10`
|
||||
against `base8` −8.3 %<!--cat:bd.base10.base8.vmaf.TOS--> and
|
||||
+6.6 %<!--cat:bd_linear.base10.base8.vmaf.TOS--> (the verifier's `bd`, `bd_linear`).
|
||||
That VMAF "saving" is an artefact of the fit, and no verdict rests on VMAF (§5).
|
||||
|
||||
## 5. Verdict against the rule
|
||||
|
||||
The verifier applies the rule run.json records to the figures above and requires the
|
||||
verdicts it states (run.json, `verdicts`):
|
||||
|
||||
| claim | result | rule |
|
||||
|---|---|---|
|
||||
| H1a, a linear or spectral catalogue basis | the rate proxy is +10.4 %<!--cat:proxy.frame128x72.catalogue.40.median--> to +18.1 %<!--cat:proxy.frame128x72.catalogue.35.median--> from the DCT's over whole frames, and its medians are within 0.9 %<!--cat:proxy_block_median_abs_max--> of it over blocks and block differences (§4.1); in front of AV1, +38.8 %<!--cat:bd.cat64.base10.psnr.median--> on PSNR at 64 components (§4.3) | **K1 and K2 hold: refuted for a studio's catalogue** |
|
||||
| H1b, exact recurrence across titles | non-flat recurrence across titles is at most 0.169 %<!--cat:recur.cross35_runtime_pct_max--> of a title's runtime and 0.010 %<!--cat:recur.cross35_bytes_pct_max--> of its bytes, even at the looser bound of 35 dB at both sizes (§4.2) | **K3 holds: refuted for a studio's catalogue; untested for an episodic series** |
|
||||
| H1c, a learned prior tuned on the catalogue | not measured | **open** |
|
||||
|
||||
GO is missed by 48.8<!--cat:go_gap_points.cat64--> points at 64 components (§4.3).
|
||||
|
||||
Why a linear basis fails here, on the mechanism the figures show:
|
||||
|
||||
1. A predictive codec already holds a better prior, the frames it has just decoded, which
|
||||
it refreshes every frame and never sends. The basis alone reaches only the coarse
|
||||
structure that prediction already gets (§4.3).
|
||||
2. Subtracting a reconstruction that changes with its coefficients, not with the motion,
|
||||
makes the residual less predictable in time than the picture. That is the extra cost
|
||||
before any coefficient is charged (§4.3, AV1's bytes only).
|
||||
3. A film's statistics change from its first half to its second, so a basis fitted to some
|
||||
titles fits others no better than a fixed transform, and more titles make it more
|
||||
generic (§4.1).
|
||||
|
||||
The mechanism is general, so the result probably extends to a franchise. That is an
|
||||
inference: it was measured on films of one studio only (§6).
|
||||
|
||||
## 6. What this does not show
|
||||
|
||||
- **The corpus.** Films of one studio, not a franchise: they share no characters or
|
||||
sets (§3.1). TOS is live action with computer graphics. ED's source is 720×405, softer
|
||||
than the others' (run.json, `corpus`). Another short film was left out for want of a
|
||||
fetched licence statement (§3.1).
|
||||
- **The signal.** Luma only, at 640×360, with the two wide films stretched to 16:9; no
|
||||
chroma and no 1080p (§3.2).
|
||||
- **The codec side.** One encoder at one preset with `tune=1`, one 60 s clip per title, and
|
||||
a ×2 10-bit pipeline for the residual arms, compared with the same pipeline (§3.5, §4.3).
|
||||
- **The simplifications.** Linear bases only. The rate proxy is a Gaussian high-rate rate
|
||||
with no prediction (§3.3). The coefficients' bytes are estimates, the larger of two, and a
|
||||
better coefficient coder cannot rescue `cat64`, whose AV1 bytes alone cost more (§4.3).
|
||||
Recurrence is matched pixel-aligned only, so an element drawn at another size or place
|
||||
would be missed (§3.4). The byte attribution to frames in measurement (b) is approximate
|
||||
(§3.4).
|
||||
- **Not measured.** H1c, a learned prior; an episodic series, whose opening could recur
|
||||
across episodes; energy; decode cost (§1).
|
||||
- **The rule** was attested, not committed, before the results (§2).
|
||||
|
||||
## 7. Verify
|
||||
|
||||
```sh
|
||||
python3 -I tools/wai_catalogue_prior_verify.py research/catalogue-prior research/catalogue-prior.md
|
||||
```
|
||||
|
||||
It checks each reading against the SHA-256 in run.json, recomputes every stored BD-rate,
|
||||
the rate proxy, the recurrence and the verdicts, and every quoted figure (the module's
|
||||
docstring). The conformance workflow runs it (`.github/workflows/wai-conformance.yml`).
|
||||
1770
wai/research/catalogue-prior/a_basis.json
Normal file
1770
wai/research/catalogue-prior/a_basis.json
Normal file
File diff suppressed because it is too large
Load diff
770
wai/research/catalogue-prior/b_recur.json
Normal file
770
wai/research/catalogue-prior/b_recur.json
Normal file
|
|
@ -0,0 +1,770 @@
|
|||
{
|
||||
"ED": {
|
||||
"frames": 15693,
|
||||
"flat": 228,
|
||||
"exact_thumb_cross_flat": 14,
|
||||
"exact_thumb_cross_nonflat": 0,
|
||||
"cross_thumb_psnr_quantiles": {
|
||||
"0.5": 19.26856803894043,
|
||||
"0.9": 24.309589385986328,
|
||||
"0.99": 32.53339385986328,
|
||||
"0.999": 38.60044479370117,
|
||||
"1.0": 40.34019470214844
|
||||
},
|
||||
"cross_ge35": {
|
||||
"frames": 83,
|
||||
"seconds": 3.4583333333333335,
|
||||
"full_psnr_median": 33.070213317871094,
|
||||
"full_psnr_ge35": 13,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 0.5,
|
||||
"len": 5,
|
||||
"with": "SIN",
|
||||
"at_s": 103.58333333333333
|
||||
},
|
||||
{
|
||||
"start_s": 159.16666666666666,
|
||||
"len": 1,
|
||||
"with": "BBB",
|
||||
"at_s": 490.25
|
||||
},
|
||||
{
|
||||
"start_s": 163.375,
|
||||
"len": 1,
|
||||
"with": "BBB",
|
||||
"at_s": 299.2083333333333
|
||||
},
|
||||
{
|
||||
"start_s": 165.66666666666666,
|
||||
"len": 1,
|
||||
"with": "BBB",
|
||||
"at_s": 299.2083333333333
|
||||
},
|
||||
{
|
||||
"start_s": 428.5833333333333,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 103.58333333333333
|
||||
},
|
||||
{
|
||||
"start_s": 564.9166666666666,
|
||||
"len": 10,
|
||||
"with": "SIN",
|
||||
"at_s": 437.6666666666667
|
||||
},
|
||||
{
|
||||
"start_s": 566.25,
|
||||
"len": 4,
|
||||
"with": "SIN",
|
||||
"at_s": 778.0
|
||||
},
|
||||
{
|
||||
"start_s": 573.5833333333334,
|
||||
"len": 1,
|
||||
"with": "SIN",
|
||||
"at_s": 778.0
|
||||
},
|
||||
{
|
||||
"start_s": 574.6666666666666,
|
||||
"len": 6,
|
||||
"with": "SIN",
|
||||
"at_s": 771.125
|
||||
},
|
||||
{
|
||||
"start_s": 579.375,
|
||||
"len": 1,
|
||||
"with": "SIN",
|
||||
"at_s": 771.125
|
||||
},
|
||||
{
|
||||
"start_s": 580.4583333333334,
|
||||
"len": 10,
|
||||
"with": "SIN",
|
||||
"at_s": 778.0
|
||||
},
|
||||
{
|
||||
"start_s": 584.0833333333334,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 778.0
|
||||
},
|
||||
{
|
||||
"start_s": 584.625,
|
||||
"len": 4,
|
||||
"with": "SIN",
|
||||
"at_s": 778.0
|
||||
},
|
||||
{
|
||||
"start_s": 589.0833333333334,
|
||||
"len": 3,
|
||||
"with": "SIN",
|
||||
"at_s": 778.0
|
||||
},
|
||||
{
|
||||
"start_s": 590.0416666666666,
|
||||
"len": 10,
|
||||
"with": "SIN",
|
||||
"at_s": 147.33333333333334
|
||||
},
|
||||
{
|
||||
"start_s": 590.5833333333334,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 147.33333333333334
|
||||
},
|
||||
{
|
||||
"start_s": 641.4166666666666,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 103.58333333333333
|
||||
},
|
||||
{
|
||||
"start_s": 641.7083333333334,
|
||||
"len": 3,
|
||||
"with": "SIN",
|
||||
"at_s": 774.5
|
||||
},
|
||||
{
|
||||
"start_s": 646.0416666666666,
|
||||
"len": 3,
|
||||
"with": "SIN",
|
||||
"at_s": 774.5
|
||||
},
|
||||
{
|
||||
"start_s": 646.75,
|
||||
"len": 5,
|
||||
"with": "SIN",
|
||||
"at_s": 771.125
|
||||
},
|
||||
{
|
||||
"start_s": 653.2916666666666,
|
||||
"len": 7,
|
||||
"with": "SIN",
|
||||
"at_s": 764.2916666666666
|
||||
}
|
||||
]
|
||||
},
|
||||
"cross_ge40": {
|
||||
"frames": 1,
|
||||
"seconds": 0.041666666666666664,
|
||||
"full_psnr_median": 34.72438430786133,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 574.6666666666666,
|
||||
"len": 1,
|
||||
"with": "SIN",
|
||||
"at_s": 771.125
|
||||
}
|
||||
]
|
||||
},
|
||||
"self_far_ge35": {
|
||||
"frames": 80,
|
||||
"seconds": 3.3333333333333335,
|
||||
"full_psnr_ge35": 11,
|
||||
"full_psnr_ge40": 0,
|
||||
"full_psnr_median": 32.833478927612305,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 0.5,
|
||||
"len": 2
|
||||
},
|
||||
{
|
||||
"start_s": 428.5833333333333,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 564.875,
|
||||
"len": 11
|
||||
},
|
||||
{
|
||||
"start_s": 566.25,
|
||||
"len": 4
|
||||
},
|
||||
{
|
||||
"start_s": 573.5416666666666,
|
||||
"len": 2
|
||||
},
|
||||
{
|
||||
"start_s": 574.6666666666666,
|
||||
"len": 6
|
||||
},
|
||||
{
|
||||
"start_s": 579.375,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 580.4583333333334,
|
||||
"len": 11
|
||||
},
|
||||
{
|
||||
"start_s": 584.0416666666666,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 584.625,
|
||||
"len": 4
|
||||
},
|
||||
{
|
||||
"start_s": 589.0833333333334,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 590.0416666666666,
|
||||
"len": 9
|
||||
},
|
||||
{
|
||||
"start_s": 641.375,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 641.7083333333334,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 646.0416666666666,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 646.75,
|
||||
"len": 6
|
||||
},
|
||||
{
|
||||
"start_s": 653.25,
|
||||
"len": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
"self_far_ge40": {
|
||||
"frames": 5,
|
||||
"seconds": 0.20833333333333334,
|
||||
"full_psnr_ge35": 5,
|
||||
"full_psnr_ge40": 0,
|
||||
"full_psnr_median": 36.25993347167969,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 0.5,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 574.6666666666666,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 641.4583333333334,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 646.75,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 653.5416666666666,
|
||||
"len": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"BBB": {
|
||||
"frames": 14315,
|
||||
"flat": 94,
|
||||
"exact_thumb_cross_flat": 73,
|
||||
"exact_thumb_cross_nonflat": 0,
|
||||
"cross_thumb_psnr_quantiles": {
|
||||
"0.5": 17.602630615234375,
|
||||
"0.9": 22.566377639770508,
|
||||
"0.99": 23.89752197265625,
|
||||
"0.999": 32.356964111328125,
|
||||
"1.0": 39.9736328125
|
||||
},
|
||||
"cross_ge35": {
|
||||
"frames": 12,
|
||||
"seconds": 0.5,
|
||||
"full_psnr_median": 34.779043197631836,
|
||||
"full_psnr_ge35": 5,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 299.1666666666667,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 103.75
|
||||
},
|
||||
{
|
||||
"start_s": 300.3333333333333,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 146.54166666666666
|
||||
},
|
||||
{
|
||||
"start_s": 460.8333333333333,
|
||||
"len": 1,
|
||||
"with": "SIN",
|
||||
"at_s": 676.8333333333334
|
||||
},
|
||||
{
|
||||
"start_s": 461.875,
|
||||
"len": 2,
|
||||
"with": "SIN",
|
||||
"at_s": 680.375
|
||||
},
|
||||
{
|
||||
"start_s": 490.25,
|
||||
"len": 5,
|
||||
"with": "SIN",
|
||||
"at_s": 3.3333333333333335
|
||||
}
|
||||
]
|
||||
},
|
||||
"cross_ge40": {
|
||||
"frames": 0,
|
||||
"seconds": 0.0,
|
||||
"full_psnr_median": null,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": []
|
||||
},
|
||||
"self_far_ge35": {
|
||||
"frames": 8,
|
||||
"seconds": 0.3333333333333333,
|
||||
"full_psnr_ge35": 7,
|
||||
"full_psnr_ge40": 0,
|
||||
"full_psnr_median": 35.861019134521484,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 299.1666666666667,
|
||||
"len": 2
|
||||
},
|
||||
{
|
||||
"start_s": 300.3333333333333,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 460.8333333333333,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 461.875,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 490.25,
|
||||
"len": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"self_far_ge40": {
|
||||
"frames": 0,
|
||||
"seconds": 0.0,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"full_psnr_median": null,
|
||||
"runs": []
|
||||
}
|
||||
},
|
||||
"SIN": {
|
||||
"frames": 21312,
|
||||
"flat": 482,
|
||||
"exact_thumb_cross_flat": 349,
|
||||
"exact_thumb_cross_nonflat": 0,
|
||||
"cross_thumb_psnr_quantiles": {
|
||||
"0.5": 19.00314712524414,
|
||||
"0.9": 26.15960693359375,
|
||||
"0.99": 32.81053161621094,
|
||||
"0.999": 38.6514892578125,
|
||||
"1.0": 40.34019470214844
|
||||
},
|
||||
"cross_ge35": {
|
||||
"frames": 112,
|
||||
"seconds": 4.666666666666667,
|
||||
"full_psnr_median": 34.31572341918945,
|
||||
"full_psnr_ge35": 36,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 3.2916666666666665,
|
||||
"len": 5,
|
||||
"with": "BBB",
|
||||
"at_s": 490.25
|
||||
},
|
||||
{
|
||||
"start_s": 103.58333333333333,
|
||||
"len": 14,
|
||||
"with": "BBB",
|
||||
"at_s": 299.2083333333333
|
||||
},
|
||||
{
|
||||
"start_s": 146.375,
|
||||
"len": 9,
|
||||
"with": "BBB",
|
||||
"at_s": 490.3333333333333
|
||||
},
|
||||
{
|
||||
"start_s": 147.0,
|
||||
"len": 9,
|
||||
"with": "BBB",
|
||||
"at_s": 461.875
|
||||
},
|
||||
{
|
||||
"start_s": 150.5,
|
||||
"len": 2,
|
||||
"with": "ED",
|
||||
"at_s": 0.5833333333333334
|
||||
},
|
||||
{
|
||||
"start_s": 437.6666666666667,
|
||||
"len": 5,
|
||||
"with": "ED",
|
||||
"at_s": 565.2916666666666
|
||||
},
|
||||
{
|
||||
"start_s": 676.7916666666666,
|
||||
"len": 5,
|
||||
"with": "BBB",
|
||||
"at_s": 490.2916666666667
|
||||
},
|
||||
{
|
||||
"start_s": 680.25,
|
||||
"len": 11,
|
||||
"with": "ED",
|
||||
"at_s": 589.1666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 750.3333333333334,
|
||||
"len": 7,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 753.875,
|
||||
"len": 5,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 757.3333333333334,
|
||||
"len": 4,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 760.625,
|
||||
"len": 6,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 764.125,
|
||||
"len": 5,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 767.5,
|
||||
"len": 6,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 770.9166666666666,
|
||||
"len": 6,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 774.3333333333334,
|
||||
"len": 5,
|
||||
"with": "ED",
|
||||
"at_s": 641.7083333333334
|
||||
},
|
||||
{
|
||||
"start_s": 777.875,
|
||||
"len": 4,
|
||||
"with": "ED",
|
||||
"at_s": 584.625
|
||||
},
|
||||
{
|
||||
"start_s": 781.25,
|
||||
"len": 4,
|
||||
"with": "ED",
|
||||
"at_s": 584.625
|
||||
}
|
||||
]
|
||||
},
|
||||
"cross_ge40": {
|
||||
"frames": 3,
|
||||
"seconds": 0.125,
|
||||
"full_psnr_median": 34.72438430786133,
|
||||
"full_psnr_ge35": 1,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 750.5833333333334,
|
||||
"len": 1,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 767.7083333333334,
|
||||
"len": 1,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
},
|
||||
{
|
||||
"start_s": 771.125,
|
||||
"len": 1,
|
||||
"with": "ED",
|
||||
"at_s": 574.6666666666666
|
||||
}
|
||||
]
|
||||
},
|
||||
"self_far_ge35": {
|
||||
"frames": 368,
|
||||
"seconds": 15.333333333333334,
|
||||
"full_psnr_ge35": 267,
|
||||
"full_psnr_ge40": 108,
|
||||
"full_psnr_median": 36.434078216552734,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 3.2916666666666665,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 39.708333333333336,
|
||||
"len": 65
|
||||
},
|
||||
{
|
||||
"start_s": 42.5,
|
||||
"len": 16
|
||||
},
|
||||
{
|
||||
"start_s": 90.625,
|
||||
"len": 4
|
||||
},
|
||||
{
|
||||
"start_s": 100.70833333333333,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 103.58333333333333,
|
||||
"len": 5
|
||||
},
|
||||
{
|
||||
"start_s": 147.0,
|
||||
"len": 9
|
||||
},
|
||||
{
|
||||
"start_s": 150.5,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 431.5833333333333,
|
||||
"len": 82
|
||||
},
|
||||
{
|
||||
"start_s": 437.6666666666667,
|
||||
"len": 5
|
||||
},
|
||||
{
|
||||
"start_s": 676.8333333333334,
|
||||
"len": 4
|
||||
},
|
||||
{
|
||||
"start_s": 680.25,
|
||||
"len": 10
|
||||
},
|
||||
{
|
||||
"start_s": 687.9583333333334,
|
||||
"len": 56
|
||||
},
|
||||
{
|
||||
"start_s": 698.7916666666666,
|
||||
"len": 36
|
||||
},
|
||||
{
|
||||
"start_s": 750.25,
|
||||
"len": 9
|
||||
},
|
||||
{
|
||||
"start_s": 753.7083333333334,
|
||||
"len": 9
|
||||
},
|
||||
{
|
||||
"start_s": 757.25,
|
||||
"len": 6
|
||||
},
|
||||
{
|
||||
"start_s": 760.5416666666666,
|
||||
"len": 8
|
||||
},
|
||||
{
|
||||
"start_s": 763.9583333333334,
|
||||
"len": 9
|
||||
},
|
||||
{
|
||||
"start_s": 767.375,
|
||||
"len": 9
|
||||
},
|
||||
{
|
||||
"start_s": 770.75,
|
||||
"len": 10
|
||||
},
|
||||
{
|
||||
"start_s": 774.375,
|
||||
"len": 4
|
||||
},
|
||||
{
|
||||
"start_s": 777.875,
|
||||
"len": 4
|
||||
},
|
||||
{
|
||||
"start_s": 781.2916666666666,
|
||||
"len": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"self_far_ge40": {
|
||||
"frames": 180,
|
||||
"seconds": 7.5,
|
||||
"full_psnr_ge35": 160,
|
||||
"full_psnr_ge40": 108,
|
||||
"full_psnr_median": 40.80683517456055,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 39.708333333333336,
|
||||
"len": 55
|
||||
},
|
||||
{
|
||||
"start_s": 147.25,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 431.5833333333333,
|
||||
"len": 55
|
||||
},
|
||||
{
|
||||
"start_s": 680.25,
|
||||
"len": 2
|
||||
},
|
||||
{
|
||||
"start_s": 687.9583333333334,
|
||||
"len": 10
|
||||
},
|
||||
{
|
||||
"start_s": 689.6666666666666,
|
||||
"len": 15
|
||||
},
|
||||
{
|
||||
"start_s": 699.4166666666666,
|
||||
"len": 21
|
||||
},
|
||||
{
|
||||
"start_s": 750.5,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 753.9583333333334,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 757.4166666666666,
|
||||
"len": 2
|
||||
},
|
||||
{
|
||||
"start_s": 760.7916666666666,
|
||||
"len": 2
|
||||
},
|
||||
{
|
||||
"start_s": 764.2083333333334,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 767.625,
|
||||
"len": 3
|
||||
},
|
||||
{
|
||||
"start_s": 771.0,
|
||||
"len": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"TOS": {
|
||||
"frames": 17620,
|
||||
"flat": 270,
|
||||
"exact_thumb_cross_flat": 245,
|
||||
"exact_thumb_cross_nonflat": 0,
|
||||
"cross_thumb_psnr_quantiles": {
|
||||
"0.5": 17.279958724975586,
|
||||
"0.9": 21.737445831298828,
|
||||
"0.99": 29.325151443481445,
|
||||
"0.999": 33.93708801269531,
|
||||
"1.0": 36.36323547363281
|
||||
},
|
||||
"cross_ge35": {
|
||||
"frames": 10,
|
||||
"seconds": 0.4166666666666667,
|
||||
"full_psnr_median": 33.064083099365234,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 65.16666666666667,
|
||||
"len": 8,
|
||||
"with": "BBB",
|
||||
"at_s": 490.375
|
||||
},
|
||||
{
|
||||
"start_s": 707.6666666666666,
|
||||
"len": 2,
|
||||
"with": "BBB",
|
||||
"at_s": 490.4166666666667
|
||||
}
|
||||
]
|
||||
},
|
||||
"cross_ge40": {
|
||||
"frames": 0,
|
||||
"seconds": 0.0,
|
||||
"full_psnr_median": null,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"runs": []
|
||||
},
|
||||
"self_far_ge35": {
|
||||
"frames": 19,
|
||||
"seconds": 0.7916666666666666,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"full_psnr_median": 32.27806854248047,
|
||||
"runs": [
|
||||
{
|
||||
"start_s": 65.16666666666667,
|
||||
"len": 14
|
||||
},
|
||||
{
|
||||
"start_s": 604.9583333333334,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 616.75,
|
||||
"len": 1
|
||||
},
|
||||
{
|
||||
"start_s": 707.625,
|
||||
"len": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
"self_far_ge40": {
|
||||
"frames": 0,
|
||||
"seconds": 0.0,
|
||||
"full_psnr_ge35": 0,
|
||||
"full_psnr_ge40": 0,
|
||||
"full_psnr_median": null,
|
||||
"runs": []
|
||||
}
|
||||
}
|
||||
}
|
||||
1478
wai/research/catalogue-prior/c_rd.json
Normal file
1478
wai/research/catalogue-prior/c_rd.json
Normal file
File diff suppressed because it is too large
Load diff
122
wai/research/catalogue-prior/d_exact.json
Normal file
122
wai/research/catalogue-prior/d_exact.json
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
{
|
||||
"ED": {
|
||||
"frames": 15693,
|
||||
"av1_bytes_crf36": 21882751,
|
||||
"kbps": 267.7300829669279,
|
||||
"flat": {
|
||||
"frames": 228,
|
||||
"seconds": 9.5,
|
||||
"runtime_pct": 1.4528770789523993,
|
||||
"bytes": 46263,
|
||||
"bytes_pct": 0.21141308969790865,
|
||||
"thumb_std_median": 0.03606089172949539
|
||||
},
|
||||
"cross35": {
|
||||
"frames": 13,
|
||||
"seconds": 0.5416666666666666,
|
||||
"runtime_pct": 0.08283948257184733,
|
||||
"bytes": 398,
|
||||
"bytes_pct": 0.0018187841190534042,
|
||||
"thumb_std_median": 3.000226016223685
|
||||
},
|
||||
"self_far35": {
|
||||
"frames": 11,
|
||||
"seconds": 0.4583333333333333,
|
||||
"runtime_pct": 0.07009494679156311,
|
||||
"bytes": 341,
|
||||
"bytes_pct": 0.0015583049864251529,
|
||||
"thumb_std_median": 2.2512774405956963
|
||||
},
|
||||
"nonflat_thumb_std_median": 30.291161437876323
|
||||
},
|
||||
"BBB": {
|
||||
"frames": 14315,
|
||||
"av1_bytes_crf36": 20683682,
|
||||
"kbps": 277.41997513098147,
|
||||
"flat": {
|
||||
"frames": 94,
|
||||
"seconds": 3.9166666666666665,
|
||||
"runtime_pct": 0.656653859587845,
|
||||
"bytes": 134242,
|
||||
"bytes_pct": 0.6490237086414304,
|
||||
"thumb_std_median": 0.0
|
||||
},
|
||||
"cross35": {
|
||||
"frames": 5,
|
||||
"seconds": 0.20833333333333334,
|
||||
"runtime_pct": 0.0349283967865875,
|
||||
"bytes": 284,
|
||||
"bytes_pct": 0.0013730630745531671,
|
||||
"thumb_std_median": 2.6479624238745973
|
||||
},
|
||||
"self_far35": {
|
||||
"frames": 7,
|
||||
"seconds": 0.2916666666666667,
|
||||
"runtime_pct": 0.0488997555012225,
|
||||
"bytes": 350,
|
||||
"bytes_pct": 0.001692155197512706,
|
||||
"thumb_std_median": 2.6479624238745973
|
||||
},
|
||||
"nonflat_thumb_std_median": 35.89060543273839
|
||||
},
|
||||
"SIN": {
|
||||
"frames": 21312,
|
||||
"av1_bytes_crf36": 30750110,
|
||||
"kbps": 277.02801801801803,
|
||||
"flat": {
|
||||
"frames": 482,
|
||||
"seconds": 20.083333333333332,
|
||||
"runtime_pct": 2.2616366366366365,
|
||||
"bytes": 53243,
|
||||
"bytes_pct": 0.17314734809078733,
|
||||
"thumb_std_median": 0.0
|
||||
},
|
||||
"cross35": {
|
||||
"frames": 36,
|
||||
"seconds": 1.5,
|
||||
"runtime_pct": 0.16891891891891891,
|
||||
"bytes": 3187,
|
||||
"bytes_pct": 0.010364190567123175,
|
||||
"thumb_std_median": 2.980607967828133
|
||||
},
|
||||
"self_far35": {
|
||||
"frames": 267,
|
||||
"seconds": 11.125,
|
||||
"runtime_pct": 1.2528153153153154,
|
||||
"bytes": 302619,
|
||||
"bytes_pct": 0.9841233088271879,
|
||||
"thumb_std_median": 58.21047824127981
|
||||
},
|
||||
"nonflat_thumb_std_median": 30.714039515592862
|
||||
},
|
||||
"TOS": {
|
||||
"frames": 17620,
|
||||
"av1_bytes_crf36": 31963904,
|
||||
"kbps": 348.3013375709421,
|
||||
"flat": {
|
||||
"frames": 270,
|
||||
"seconds": 11.25,
|
||||
"runtime_pct": 1.532349602724177,
|
||||
"bytes": 88896,
|
||||
"bytes_pct": 0.2781137122674377,
|
||||
"thumb_std_median": 0.0
|
||||
},
|
||||
"cross35": {
|
||||
"frames": 0,
|
||||
"seconds": 0.0,
|
||||
"runtime_pct": 0.0,
|
||||
"bytes": 0,
|
||||
"bytes_pct": 0.0,
|
||||
"thumb_std_median": null
|
||||
},
|
||||
"self_far35": {
|
||||
"frames": 0,
|
||||
"seconds": 0.0,
|
||||
"runtime_pct": 0.0,
|
||||
"bytes": 0,
|
||||
"bytes_pct": 0.0,
|
||||
"thumb_std_median": null
|
||||
},
|
||||
"nonflat_thumb_std_median": 37.15009941663702
|
||||
}
|
||||
}
|
||||
91
wai/research/catalogue-prior/run.json
Normal file
91
wai/research/catalogue-prior/run.json
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"format": "wai-catalogue-prior/1",
|
||||
"about": "The run behind research/catalogue-prior.md: its corpus, tools, timeline, decision rule and verdicts, and the SHA-256 of each reading file beside this one, as the run wrote it.",
|
||||
"readings": {
|
||||
"a_basis.json": "5186b5b77adfc9aabd685b3e4a3d1fa34fa82c345093b6b28c9a0c1c2467ca68",
|
||||
"b_recur.json": "10247e1f4bfdd8b847e362daa870d80b7f3b83e12cd896f924c8215e267bc9e2",
|
||||
"c_rd.json": "c81b95872c384a1f8c70acc1b95fd11dae6488388d0d605b9ebb74a5af2a83d0",
|
||||
"d_exact.json": "74444442b5150dff0412834861170cfeec7a71bc8ac728d14671fb640df3520d"
|
||||
},
|
||||
"corpus": [
|
||||
{
|
||||
"code": "ED",
|
||||
"title": "Elephants Dream",
|
||||
"year": 2006,
|
||||
"file": "elephantsdream-720-h264-st-aac.mov",
|
||||
"bytes": 152496338,
|
||||
"sha256": "6fc295e0e92835316ec95929ccfc664b05ed2434f673e6f5e684f98f7558a547",
|
||||
"raster": "720x405",
|
||||
"frames": 15693,
|
||||
"licence": "Creative Commons; the version is not stated on the page fetched",
|
||||
"licence_page_sha256": "96a8277544d978deb79503c2b82667501429d6c1cbeeabb9adba5eaa5cb01e36",
|
||||
"licence_fetched": "2026-11-04T01:38:17Z"
|
||||
},
|
||||
{
|
||||
"code": "BBB",
|
||||
"title": "Big Buck Bunny",
|
||||
"year": 2008,
|
||||
"file": "big_buck_bunny_720p_h264.mov, read from the archive whose SHA-256 is given",
|
||||
"bytes": 416751396,
|
||||
"sha256": "b0c9ade80b086179feee41514929de583966eec87c703575d97f051f88b33b67",
|
||||
"raster": "1280x720",
|
||||
"frames": 14315,
|
||||
"licence": "Creative Commons Attribution 3.0",
|
||||
"licence_page_sha256": "bd9eb73ddec647bbb486c47316163042a9655a8483358c093121341d0dc080cf",
|
||||
"licence_fetched": "2026-11-04T01:38:17Z"
|
||||
},
|
||||
{
|
||||
"code": "SIN",
|
||||
"title": "Sintel",
|
||||
"year": 2010,
|
||||
"file": "the 1080p release",
|
||||
"bytes": 1172428172,
|
||||
"sha256": "fae11ed41b2c99dfbeabce7ea5458446e21e7b840a982f510f2341919c3d9920",
|
||||
"raster": "1920x818",
|
||||
"frames": 21312,
|
||||
"licence": "Creative Commons Attribution 3.0",
|
||||
"licence_page_sha256": "67f30f30c55022c6f7de66beb997f04117b24f7aca928a912286a53edaa4b627",
|
||||
"licence_fetched": "2026-11-03T12:26:59Z"
|
||||
},
|
||||
{
|
||||
"code": "TOS",
|
||||
"title": "Tears of Steel",
|
||||
"year": 2012,
|
||||
"file": "the 1080p release",
|
||||
"bytes": 583774083,
|
||||
"sha256": "99486359be7e3681168a0fe94e1cbb0284c48b57b2a3ce7df4fa75e185987a15",
|
||||
"raster": "1920x800",
|
||||
"frames": 17620,
|
||||
"licence": "Creative Commons Attribution 3.0",
|
||||
"licence_page_sha256": "c3defc0521e16af0a3afb6a96d084517620adf6434038e6a3a2ba1cf50f86947",
|
||||
"licence_fetched": "2026-11-03T12:26:44Z"
|
||||
}
|
||||
],
|
||||
"analysis_raster": "luma at 640x360, area scaling, 8-bit limited range as decoded; 128x72 by 5x5 area mean (float) and 64x36 by 10x10 area mean (rounded to 8 bits) derived from it",
|
||||
"tools": {
|
||||
"ffmpeg": "9.0.2",
|
||||
"svt_av1": "4.2.0",
|
||||
"dav1d": "1.5.4",
|
||||
"libvmaf": "3.2.1, model vmaf_v0.6.1",
|
||||
"python": "3.14.4",
|
||||
"numpy": "2.3.5"
|
||||
},
|
||||
"host": "a shared Linux x86_64 build host, 32 threads; each step in a user scope with MemoryMax=64G, CPUWeight=50 and nice 10; BLAS capped at 8 threads",
|
||||
"timeline": [
|
||||
{"utc": "2026-11-04T01:38:17Z", "event": "licence pages fetched"},
|
||||
{"utc": "2026-11-04T01:43:38Z", "event": "the rate proxy's target PSNRs (30, 35, 40 and 45 dB) recorded with the basis script in a snapshot of the measurement scripts"},
|
||||
{"utc": "2026-11-04T01:49:04Z", "event": "moment accumulation finished: the last moments file written; no figure printed"},
|
||||
{"utc": "2026-11-04T01:58Z", "event": "the decision rule written, before any result of measurements (a) or (c) was seen; attested, not committed before the results"},
|
||||
{"utc": "2026-11-04T01:58:51Z", "event": "the residual-coding script last changed"},
|
||||
{"utc": "2026-11-04T02:00:43Z", "event": "the first result file written: a_basis.json"},
|
||||
{"utc": "2026-11-04T02:01:24Z", "event": "the basis script's only later change: a print format"},
|
||||
{"utc": "2026-11-04T02:38:59Z", "event": "c_rd.json written"}
|
||||
],
|
||||
"rule": {
|
||||
"go": {"bd_rate_max": -10, "metrics": ["psnr", "vmaf"], "titles_min": 3, "breakeven_titles_per_sink_max": 5},
|
||||
"k1": {"below_dct_pct": 5, "targets": [35, 40], "of": "the catalogue basis's rate proxy on the median title, at every granularity"},
|
||||
"k2": {"bd_rate_above": 0, "metrics": ["psnr", "vmaf"], "of": "the catalogue-basis residual and its coefficients against AV1 through the same pipeline, on the median title"},
|
||||
"k3": {"runtime_pct_below": 1, "thumb_psnr_min": 40, "full_psnr_min": 35, "of": "non-flat material recurring across titles, per title"}
|
||||
},
|
||||
"verdicts": {"go": false, "k1": true, "k2": true, "k3": true, "no_psnr_saving_any_title": true}
|
||||
}
|
||||
|
|
@ -517,6 +517,15 @@ to say what each one reaches. Neither is a defect.
|
|||
that. **No WAI capability is registered as using it** (determinism-tiers §5
|
||||
lists none). A contribution should say that plainly rather than imply
|
||||
coverage.
|
||||
*Corrected 2026-11-03, body read:* the body states what happens to the
|
||||
reconstruction: "floating-point differences still cause gradual divergence in
|
||||
the features and reconstructed frames", and "without a synchronization
|
||||
mechanism, the divergence can grow arbitrarily large" (§3.2). It bounds the
|
||||
divergence with periodic I-frames that "fully resynchronize encoder and
|
||||
decoder feature states" (§3.2), and those I-frames are decoded by the same
|
||||
float model (Appendix B.8). So the route guarantees the entropy stage and not
|
||||
the reconstruction, which the paper states and does not leave to inference; a
|
||||
resynchronisation re-anchors the picture to one float decode.
|
||||
- **Reproducibility by cross-check.** The IEEE 1857.11 call for proposals came
|
||||
out of the working subgroup's meeting on 9 Dec 2021, and its timeline lists
|
||||
the announcement on 21 Dec 2021. It states that "every proponent who submits
|
||||
|
|
@ -817,6 +826,10 @@ defines the whole decode in CI (G8). What remains is stated in the texts, not
|
|||
contradicted by them: the float fallback exists and the result does not record
|
||||
which path ran, and a `norm.json` with a missing or mistyped key panics in the
|
||||
loaders instead of falling back (the `try_jpegai_exact` doc comment says so).
|
||||
*Since 2026-11-04:* the result records which path ran. The dispatch returns it
|
||||
(`neural::Dispatched::path`, determinism-tiers §5), a session claim's decoder
|
||||
entry states it as its `variant` (jwp-receipts §2.4.12), and the reference check
|
||||
of a reconstruction binding reads it (reconstruction-binding §2).
|
||||
|
||||
**G12: No normative behaviour for a parameter-set mismatch, and a
|
||||
per-rendition pin the code carries but never checks.** UC-2 sets out the
|
||||
|
|
@ -926,7 +939,8 @@ Read on 2026-09-27 unless noted.
|
|||
2510.13867. The PDF was read for the conformance-point, integer-pipeline and
|
||||
excluded-features passages. <https://arxiv.org/abs/2510.13867>
|
||||
7. *MLVC: Multi-platform Learned Video Codec for Real-World Deployment*, arXiv
|
||||
2606.28027, ECCV 2026. Abstract page only.
|
||||
2606.28027, ECCV 2026. Abstract page only. *Corrected 2026-11-03:* the body's
|
||||
§3.1, §3.2, Appendix B.8 and Supplement D.1 (version 2) were read that day.
|
||||
<https://arxiv.org/abs/2606.28027>
|
||||
8. IEEE 1857.11 call for proposals, the outcome of the 9 Dec 2021 working
|
||||
subgroup meeting; its timeline lists the announcement on 21 Dec 2021 (the
|
||||
|
|
|
|||
|
|
@ -70,13 +70,16 @@ identity (Appendix D). Across the corpus: the record-wide deduplication totals (
|
|||
[`../wai-rs/tests/vectors/`](../wai-rs/tests/vectors/), generated too (that directory's
|
||||
README states how), as the staged-delivery corpus wraps it: a `wai.neural.int_hyper` picture, a `wai.video.int_hyper` clip, a
|
||||
`wai.audio.int_codec` clip and a `wai.splat.int_codec` attribute tensor, each pinned to
|
||||
its parameter set. Each source is the base's output with fine detail added by integer
|
||||
its parameter set; and the unpinned `wai.neural.int_hyper` picture of
|
||||
`image_int_hyper_unpinned`, decoded against the set the sink holds, which its decoder entry
|
||||
names by the digest a pin over it names (SPEC §3.1, *Pin by default*): the digest the
|
||||
pinned picture's pin names, over the same files. Each source is the base's output with fine detail added by integer
|
||||
functions of position, channel and a seed (for audio, two triangle waves), raised to the
|
||||
final shape by the layer's own predictor where the title changes the shape. The layers
|
||||
are `WIR1` payloads coded toward the source by the reference encoder
|
||||
(`staged::wir1::encode`): up2 then identity for the picture; one identity layer per
|
||||
frame, twice, for the clip; a rate layer to twice the sample rate, then identity, for
|
||||
the audio; identity for the attribute tensor. Each title's single-layer references code
|
||||
the audio; identity for the attribute tensor and for the unpinned picture. Each title's single-layer references code
|
||||
one layer straight from stage 0 at the final step and, for some titles, at further
|
||||
steps (the curve of §6.3).
|
||||
|
||||
|
|
|
|||
BIN
wai/staged-measure-conformance/fixtures/unpinned/L01.wai
Normal file
BIN
wai/staged-measure-conformance/fixtures/unpinned/L01.wai
Normal file
Binary file not shown.
BIN
wai/staged-measure-conformance/fixtures/unpinned/L02.wai
Normal file
BIN
wai/staged-measure-conformance/fixtures/unpinned/L02.wai
Normal file
Binary file not shown.
BIN
wai/staged-measure-conformance/fixtures/unpinned/R0_00.wai
Normal file
BIN
wai/staged-measure-conformance/fixtures/unpinned/R0_00.wai
Normal file
Binary file not shown.
29
wai/staged-measure-conformance/fixtures/unpinned/expect.json
Normal file
29
wai/staged-measure-conformance/fixtures/unpinned/expect.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"format": "wai-measure-expect/1",
|
||||
"about": "The fixture tool's own reconstruction: the SHA-256 of each unit's canonical buffer at each stage it coded, and of each reference's final units, from the encoder's reconstruction loop.",
|
||||
"presentations": [
|
||||
{
|
||||
"unit": 0,
|
||||
"layers": 1,
|
||||
"out_sha256": "dce7282d46f88f03d5dc0fd42f325756d310fe1fc982b6e1792a1fbfbeb482cf"
|
||||
},
|
||||
{
|
||||
"unit": 0,
|
||||
"layers": 2,
|
||||
"out_sha256": "71639c87086d0fe69eb0e9ba8a976e882cb7ac38056a3c388d901dd3f0ac4fd2"
|
||||
},
|
||||
{
|
||||
"unit": 0,
|
||||
"layers": 3,
|
||||
"out_sha256": "7655e2d7055f1ea34084785b13e0becbec58301023ee9beff3e75a11abb7b621"
|
||||
}
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"label": "one layer, q 1",
|
||||
"out_sha256": [
|
||||
"7655e2d7055f1ea34084785b13e0becbec58301023ee9beff3e75a11abb7b621"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
wai/staged-measure-conformance/fixtures/unpinned/source.bin
Normal file
BIN
wai/staged-measure-conformance/fixtures/unpinned/source.bin
Normal file
Binary file not shown.
31
wai/staged-measure-conformance/fixtures/unpinned/title.json
Normal file
31
wai/staged-measure-conformance/fixtures/unpinned/title.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"format": "wai-measure-title/1",
|
||||
"title": "unpinned",
|
||||
"about": "an unpinned wai.neural.int_hyper picture (16×16), decoded against the set the sink holds (SPEC §3.1, Pin by default), its decoder entry naming that set; an identity layer at q 6, then one at q 1; reference: one identity layer at q 1",
|
||||
"unit_us": 40000,
|
||||
"bases": [
|
||||
{
|
||||
"case": "staged-conformance/cases/image_int_hyper_unpinned",
|
||||
"base": 0,
|
||||
"first_unit": 0,
|
||||
"capture_us": null
|
||||
}
|
||||
],
|
||||
"layers": [
|
||||
"L01.wai",
|
||||
"L02.wai"
|
||||
],
|
||||
"source": {
|
||||
"file": "source.bin",
|
||||
"sha256": "7655e2d7055f1ea34084785b13e0becbec58301023ee9beff3e75a11abb7b621"
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"label": "one layer, q 1",
|
||||
"layers": [
|
||||
"R0_00.wai"
|
||||
]
|
||||
}
|
||||
],
|
||||
"expect": "expect.json"
|
||||
}
|
||||
|
|
@ -1809,14 +1809,313 @@
|
|||
"referenced_bytes": 8846,
|
||||
"stored_bytes": 6515
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "unpinned",
|
||||
"content_id": "da9fd8c0531e288c5d4666624592ec5fae818a8320ad8e6e4f778ddf70533fd9",
|
||||
"framing": "registered",
|
||||
"unit_class": "rgb8",
|
||||
"unit_us": 40000,
|
||||
"bases": [
|
||||
{
|
||||
"object": "3fc4569e249bf6b7fbb3b5f7145688ba8f8b63368a0714ec52d9376034ebacf2",
|
||||
"capability": "wai.neural.int_hyper",
|
||||
"pin": "13e8b0d18e001eb9f0160309a02755b35a14f71ba0bcaa13f373e32b931ab818",
|
||||
"tier": "none",
|
||||
"first_unit": 0,
|
||||
"capture_us": null,
|
||||
"units": 1,
|
||||
"param_set_bytes": 2676
|
||||
}
|
||||
],
|
||||
"objects": [
|
||||
{
|
||||
"file": "staged-conformance/cases/image_int_hyper_unpinned/base.wai",
|
||||
"object": "3fc4569e249bf6b7fbb3b5f7145688ba8f8b63368a0714ec52d9376034ebacf2",
|
||||
"capability": "wai.neural.int_hyper",
|
||||
"base": 0,
|
||||
"layer": 0,
|
||||
"units": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"counted": {
|
||||
"header": 12,
|
||||
"manifest": 158,
|
||||
"payload": 88,
|
||||
"total": 258
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "staged-measure-conformance/fixtures/unpinned/L01.wai",
|
||||
"object": "73e9f5b16db2e19ae2162a7499d9afe846675bbc956e47ce17b7c543e9300a51",
|
||||
"capability": "wai.image.int_refine",
|
||||
"base": 0,
|
||||
"layer": 1,
|
||||
"units": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"counted": {
|
||||
"header": 12,
|
||||
"manifest": 323,
|
||||
"payload": 284,
|
||||
"total": 619
|
||||
}
|
||||
},
|
||||
{
|
||||
"file": "staged-measure-conformance/fixtures/unpinned/L02.wai",
|
||||
"object": "60c436fc8ff204639290211b6dbf0372c1834a3c0fecda783523280561377bea",
|
||||
"capability": "wai.image.int_refine",
|
||||
"base": 0,
|
||||
"layer": 2,
|
||||
"units": [
|
||||
0,
|
||||
1
|
||||
],
|
||||
"counted": {
|
||||
"header": 12,
|
||||
"manifest": 323,
|
||||
"payload": 212,
|
||||
"total": 547
|
||||
}
|
||||
}
|
||||
],
|
||||
"bfpf_min": 258,
|
||||
"prefixes": [
|
||||
{
|
||||
"layers": 1,
|
||||
"stage_counted": 258,
|
||||
"counted": 258,
|
||||
"per_base": [
|
||||
{
|
||||
"stage_counted": 258,
|
||||
"counted": 258
|
||||
}
|
||||
],
|
||||
"units": 1,
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "39980",
|
||||
"sse_y": "5688",
|
||||
"ssim_y_q32": 4287319116,
|
||||
"windows": 9
|
||||
},
|
||||
"vs_final": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "39980",
|
||||
"sse_y": "5688",
|
||||
"ssim_y_q32": 4287319116,
|
||||
"windows": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
"layers": 2,
|
||||
"stage_counted": 619,
|
||||
"counted": 877,
|
||||
"per_base": [
|
||||
{
|
||||
"stage_counted": 619,
|
||||
"counted": 877
|
||||
}
|
||||
],
|
||||
"units": 1,
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "1160",
|
||||
"sse_y": "210",
|
||||
"ssim_y_q32": 4294692926,
|
||||
"windows": 9
|
||||
},
|
||||
"vs_final": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "1160",
|
||||
"sse_y": "210",
|
||||
"ssim_y_q32": 4294692926,
|
||||
"windows": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
"layers": 3,
|
||||
"stage_counted": 547,
|
||||
"counted": 1424,
|
||||
"per_base": [
|
||||
{
|
||||
"stage_counted": 547,
|
||||
"counted": 1424
|
||||
}
|
||||
],
|
||||
"units": 1,
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "0",
|
||||
"sse_y": "0",
|
||||
"ssim_y_q32": 4294967296,
|
||||
"windows": 9
|
||||
},
|
||||
"vs_final": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "0",
|
||||
"sse_y": "0",
|
||||
"ssim_y_q32": 4294967296,
|
||||
"windows": 9
|
||||
}
|
||||
}
|
||||
],
|
||||
"decoders": [
|
||||
{
|
||||
"capability": "wai.neural.int_hyper",
|
||||
"pin": "13e8b0d18e001eb9f0160309a02755b35a14f71ba0bcaa13f373e32b931ab818",
|
||||
"object": "3fc4569e249bf6b7fbb3b5f7145688ba8f8b63368a0714ec52d9376034ebacf2"
|
||||
},
|
||||
{
|
||||
"capability": "wai.image.int_refine",
|
||||
"pin": null,
|
||||
"object": "73e9f5b16db2e19ae2162a7499d9afe846675bbc956e47ce17b7c543e9300a51"
|
||||
},
|
||||
{
|
||||
"capability": "wai.image.int_refine",
|
||||
"pin": null,
|
||||
"object": "60c436fc8ff204639290211b6dbf0372c1834a3c0fecda783523280561377bea"
|
||||
}
|
||||
],
|
||||
"presentations": [
|
||||
{
|
||||
"unit": 0,
|
||||
"layers": 1,
|
||||
"decoder": 0,
|
||||
"shape": [
|
||||
16,
|
||||
16,
|
||||
3
|
||||
],
|
||||
"out_sha256": "dce7282d46f88f03d5dc0fd42f325756d310fe1fc982b6e1792a1fbfbeb482cf",
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "39980",
|
||||
"sse_y": "5688",
|
||||
"ssim_y_q32": 4287319116,
|
||||
"windows": 9
|
||||
},
|
||||
"vs_final": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "39980",
|
||||
"sse_y": "5688",
|
||||
"ssim_y_q32": 4287319116,
|
||||
"windows": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
"unit": 0,
|
||||
"layers": 2,
|
||||
"decoder": 1,
|
||||
"shape": [
|
||||
16,
|
||||
16,
|
||||
3
|
||||
],
|
||||
"out_sha256": "71639c87086d0fe69eb0e9ba8a976e882cb7ac38056a3c388d901dd3f0ac4fd2",
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "1160",
|
||||
"sse_y": "210",
|
||||
"ssim_y_q32": 4294692926,
|
||||
"windows": 9
|
||||
},
|
||||
"vs_final": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "1160",
|
||||
"sse_y": "210",
|
||||
"ssim_y_q32": 4294692926,
|
||||
"windows": 9
|
||||
}
|
||||
},
|
||||
{
|
||||
"unit": 0,
|
||||
"layers": 3,
|
||||
"decoder": 2,
|
||||
"shape": [
|
||||
16,
|
||||
16,
|
||||
3
|
||||
],
|
||||
"out_sha256": "7655e2d7055f1ea34084785b13e0becbec58301023ee9beff3e75a11abb7b621",
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "0",
|
||||
"sse_y": "0",
|
||||
"ssim_y_q32": 4294967296,
|
||||
"windows": 9
|
||||
},
|
||||
"vs_final": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "0",
|
||||
"sse_y": "0",
|
||||
"ssim_y_q32": 4294967296,
|
||||
"windows": 9
|
||||
}
|
||||
}
|
||||
],
|
||||
"output_root": "0e7a20ad5c9593c26900154e2ffb606928445498472264285d13c248d9136ca6",
|
||||
"leaf_count": 3,
|
||||
"overhead": [
|
||||
{
|
||||
"label": "staged",
|
||||
"counted": 1424,
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "0",
|
||||
"sse_y": "0",
|
||||
"ssim_y_q32": 4294967296,
|
||||
"windows": 9
|
||||
},
|
||||
"out_sha256": [
|
||||
"7655e2d7055f1ea34084785b13e0becbec58301023ee9beff3e75a11abb7b621"
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "one layer, q 1",
|
||||
"counted": 1069,
|
||||
"vs_source": {
|
||||
"kind": "rgb8",
|
||||
"n_samples": 768,
|
||||
"sse_rgb": "0",
|
||||
"sse_y": "0",
|
||||
"ssim_y_q32": 4294967296,
|
||||
"windows": 9
|
||||
},
|
||||
"out_sha256": [
|
||||
"7655e2d7055f1ea34084785b13e0becbec58301023ee9beff3e75a11abb7b621"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dedup": {
|
||||
"objects": 5,
|
||||
"unique": 4,
|
||||
"referenced_bytes": 2493,
|
||||
"stored_bytes": 2235
|
||||
}
|
||||
}
|
||||
],
|
||||
"record": {
|
||||
"dedup": {
|
||||
"objects": 46,
|
||||
"unique": 32,
|
||||
"referenced_bytes": 133542,
|
||||
"stored_bytes": 113587
|
||||
"objects": 51,
|
||||
"unique": 36,
|
||||
"referenced_bytes": 136035,
|
||||
"stored_bytes": 115822
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ specifications. It implements the rest itself:
|
|||
- the quality integers (§4): integer BT.601 luma, SSE, the Q32 structural similarity
|
||||
over clipped 8×8 windows at stride 4 with an edge-aligned last window on each axis
|
||||
(Python's // floors toward −∞, as the method states), PCM and tensor sums, each pooled over several units;
|
||||
- the output tree (Appendix C): RFC 6962's tree hash over SHA-256 leaves;
|
||||
- the output tree (Appendix C): RFC 6962's tree hash over SHA-256 leaves, its decoder
|
||||
entries each naming the parameter set its decode read (jwp-receipts §2.4.12): the pin
|
||||
it verified, or, unpinned, the digest a pin over the bytes read would name (SPEC §3.1);
|
||||
- the prefixes' counted bytes, first presentable, the layering overhead's points,
|
||||
deduplication (§9), and the title's content identity.
|
||||
|
||||
|
|
@ -70,6 +72,18 @@ UNIT_CLASS = {
|
|||
}
|
||||
KIND = {"rgb8": "rgb8", "pcm16": "pcm", "tensor-i64": "attr"}
|
||||
|
||||
# SPEC §5: the members of the parameter set each base capability the corpus uses decodes
|
||||
# against, and those registered with a one-file set, which a pin names by that file's
|
||||
# SHA-256 rather than by a listing (SPEC §3.1).
|
||||
SET_MEMBERS = {
|
||||
"wai.neural.int_hyper": ["entropy.json", "g_s.whm", "h_s.whm"],
|
||||
"wai.video.int_hyper": ["entropy.json", "g_s.whm", "h_s.whm"],
|
||||
"wai.video.int_motion": [],
|
||||
"wai.audio.int_codec": ["model.wam"],
|
||||
"wai.splat.int_codec": ["g_s.whm"],
|
||||
}
|
||||
ONE_FILE = {"wai.audio.int_codec", "wai.splat.int_codec"}
|
||||
|
||||
# staged-measurement Appendix D: the tools' stated limits, and the order Rust's
|
||||
# DeclaredCost::exceeds compares the figures in.
|
||||
TOOL_COST = [1 << 25, 1 << 25, 1 << 28, 1 << 38]
|
||||
|
|
@ -138,6 +152,21 @@ def base_checks(env, cost):
|
|||
raise Refusal("missing_capability", "no capability this decode takes")
|
||||
|
||||
|
||||
def set_read(cap, held):
|
||||
"""The parameter set a decode through `cap` read from `held` (member -> bytes), by the
|
||||
digest a pin over those bytes names (SPEC §3.1, Pin by default; jwp-receipts §2.4.12): the
|
||||
SHA-256 of the one file of a capability registered with a one-file set, and otherwise the
|
||||
SHA-256 of the listing (`wai.prior.set`) of the members read, one line each,
|
||||
`<sha256> <path>`, in byte order of the paths. None where it read no set."""
|
||||
read = {m: held[m] for m in SET_MEMBERS.get(cap, []) if m in held}
|
||||
if not read:
|
||||
return None
|
||||
if cap in ONE_FILE:
|
||||
return sha(read[SET_MEMBERS[cap][0]])
|
||||
listing = b"".join(sha(read[m]).encode() + b" " + m.encode() + b"\n" for m in sorted(read, key=lambda x: x.encode()))
|
||||
return sha(listing)
|
||||
|
||||
|
||||
def sha(b):
|
||||
return hashlib.sha256(b).hexdigest()
|
||||
|
||||
|
|
@ -411,8 +440,10 @@ def evaluate(f):
|
|||
if sha(stage0) != cb["stage0_sha256"]:
|
||||
raise ValueError(f"{path}: stage0")
|
||||
set_bytes = 0
|
||||
for _, sp in sorted(cb["decode"]["set"].items()):
|
||||
set_bytes += len(f.obj(sp))
|
||||
held = {}
|
||||
for member, sp in sorted(cb["decode"]["set"].items()):
|
||||
held[member] = f.obj(sp)
|
||||
set_bytes += len(held[member])
|
||||
units, at = [], 0
|
||||
for u in cb["units"]:
|
||||
shape = tuple(u["shape"])
|
||||
|
|
@ -425,8 +456,10 @@ def evaluate(f):
|
|||
at += n
|
||||
if sum(len(x) * (2 if u["elem"] == "i16" else 1) for x, u in zip((v for _, v in units), cb["units"])) > budget:
|
||||
raise Refusal("chain-over-limit", "the base's units pass the chain budget")
|
||||
# The set the decode read, as a decoder entry names it: the pin it verified, or,
|
||||
# unpinned, the digest a pin over the bytes it read would name.
|
||||
prior = (m.get("conditioning") or {}).get("prior")
|
||||
pin = prior["sha256"] if cb["pin_resolved"] and prior else None
|
||||
pin = prior["sha256"] if cb["pin_resolved"] and prior else set_read(cap, held)
|
||||
bases.append({"hash": cb["object"], "cap": cap, "pin": pin, "first": b["first_unit"], "units": units})
|
||||
base_extra.append({"object": cb["object"], "capability": cap, "pin": pin, "tier": cb["tier"],
|
||||
"first_unit": b["first_unit"], "capture_us": b["capture_us"], "units": len(units),
|
||||
|
|
|
|||
|
|
@ -35,8 +35,9 @@ fenced block is one of these, by its info string (the words after the fence):
|
|||
- ```` ```json ````: parses (`fragment` excepts one).
|
||||
- ```` ```<lang> 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, <x>) stands for any one word.
|
||||
|
|
|
|||
286
wai/tools/wai_catalogue_prior_verify.py
Normal file
286
wai/tools/wai_catalogue_prior_verify.py
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Check the catalogue-prior record against its own readings, and every figure the text
|
||||
quotes from it.
|
||||
|
||||
python3 -I wai_catalogue_prior_verify.py <record-dir> [<text.md> ...]
|
||||
python3 -I wai_catalogue_prior_verify.py --summary <record-dir>
|
||||
|
||||
Written from the procedure in research/catalogue-prior.md, sharing no code with the
|
||||
measurement scripts that wrote the readings. It checks each reading file against the
|
||||
SHA-256 the record's run.json states. From c_rd.json's rate and quality points alone it
|
||||
recomputes every BD-rate of the residual arms and of the two AV1 bars against each other:
|
||||
by the run's own method (the natural log of bytes fitted as a least-squares cubic in
|
||||
quality, each fit integrated over the quality range the two curves share), here solved in
|
||||
exact rational arithmetic, and by piecewise-linear interpolation of the same points. The
|
||||
cubic figures must equal the ones the run stored. It recomputes the rate proxy of
|
||||
measurement (a) from a_basis.json and the recurrence of measurement (b) from d_exact.json
|
||||
and b_recur.json, applies the decision rule run.json records (GO, K1, K2, K3) and requires
|
||||
the verdicts it states. Then each `<!--cat:PATH-->` marker in the text files must follow
|
||||
a number equal to the computed value at PATH, rounded as printed. Standard library only.
|
||||
Exit 0 when everything holds, 1 with one line per finding. With `--summary` it prints
|
||||
the computed values as JSON and checks nothing.
|
||||
"""
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from fractions import Fraction
|
||||
|
||||
FINDINGS = []
|
||||
|
||||
TITLES = ("ED", "BBB", "SIN", "TOS")
|
||||
ARMS = ("cat0", "cat64", "cat256", "self64")
|
||||
CATALOGUE_ARMS = ("cat0", "cat64", "cat256")
|
||||
K_OF = {"cat0": 0, "cat64": 64, "cat256": 256, "self64": 64}
|
||||
METRICS = ("psnr", "vmaf")
|
||||
GRANULARITIES = ("frame128x72", "block8", "block16", "block8_diff")
|
||||
READINGS = ("a_basis.json", "b_recur.json", "c_rd.json", "d_exact.json")
|
||||
BASIS_DIMS = 128 * 72
|
||||
|
||||
|
||||
def bad(msg):
|
||||
FINDINGS.append(msg)
|
||||
|
||||
|
||||
def median(xs):
|
||||
xs = sorted(xs)
|
||||
n = len(xs)
|
||||
return xs[(n - 1) // 2] if n % 2 == 1 else 0.5 * (xs[n // 2 - 1] + xs[n // 2])
|
||||
|
||||
|
||||
def cubic_fit(xs, ys):
|
||||
"""The least-squares cubic through the points, c0 + c1 x + c2 x^2 + c3 x^3, solved from
|
||||
the normal equations in exact rational arithmetic over the floats given."""
|
||||
X = [Fraction(x) for x in xs]
|
||||
Y = [Fraction(y) for y in ys]
|
||||
m = [[sum(x ** (i + j) for x in X) for j in range(4)] + [sum(y * x ** i for x, y in zip(X, Y))] for i in range(4)]
|
||||
for col in range(4):
|
||||
piv = next(r for r in range(col, 4) if m[r][col] != 0)
|
||||
m[col], m[piv] = m[piv], m[col]
|
||||
for r in range(4):
|
||||
if r != col and m[r][col] != 0:
|
||||
f = m[r][col] / m[col][col]
|
||||
m[r] = [a - f * b for a, b in zip(m[r], m[col])]
|
||||
return [m[i][4] / m[i][i] for i in range(4)]
|
||||
|
||||
|
||||
def cubic_integral(c, lo, hi):
|
||||
def prim(x):
|
||||
return sum(ci * x ** (i + 1) / (i + 1) for i, ci in enumerate(c))
|
||||
return prim(hi) - prim(lo)
|
||||
|
||||
|
||||
def linear_integral(qs, ys, lo, hi):
|
||||
"""The integral over [lo, hi] of the piecewise-linear interpolant of (q, y), points
|
||||
taken in order of q."""
|
||||
pts = sorted(zip(qs, ys))
|
||||
total = []
|
||||
for (q0, y0), (q1, y1) in zip(pts, pts[1:]):
|
||||
a, b = max(q0, lo), min(q1, hi)
|
||||
if b <= a or q1 == q0:
|
||||
continue
|
||||
ya = y0 + (y1 - y0) * (a - q0) / (q1 - q0)
|
||||
yb = y0 + (y1 - y0) * (b - q0) / (q1 - q0)
|
||||
total.append(0.5 * (ya + yb) * (b - a))
|
||||
return math.fsum(total)
|
||||
|
||||
|
||||
def bd_rate(r_ref, q_ref, r_test, q_test, how):
|
||||
"""The percentage of bytes the test curve spends more than the reference at the same
|
||||
quality, over the quality range both reach; None when they share none."""
|
||||
lo, hi = max(min(q_ref), min(q_test)), min(max(q_ref), max(q_test))
|
||||
if hi <= lo:
|
||||
return None
|
||||
lr, lt = [math.log(r) for r in r_ref], [math.log(r) for r in r_test]
|
||||
if how == "cubic":
|
||||
flo, fhi = Fraction(lo), Fraction(hi)
|
||||
avg = float((cubic_integral(cubic_fit(q_test, lt), flo, fhi) - cubic_integral(cubic_fit(q_ref, lr), flo, fhi)) / (fhi - flo))
|
||||
else:
|
||||
avg = (linear_integral(q_test, lt, lo, hi) - linear_integral(q_ref, lr, lo, hi)) / (hi - lo)
|
||||
return (math.exp(avg) - 1) * 100
|
||||
|
||||
|
||||
def with_median(per_title):
|
||||
out = dict(per_title)
|
||||
vals = [v for v in per_title.values() if v is not None]
|
||||
out["median"] = median(vals) if vals else None
|
||||
return out
|
||||
|
||||
|
||||
def summarise(rec):
|
||||
c, a, d, b = rec["c_rd.json"], rec["a_basis.json"], rec["d_exact.json"], rec["b_recur.json"]
|
||||
s = {}
|
||||
|
||||
def pts(t, arm, key):
|
||||
return [p[key] for p in c[t]["arms"][arm]["points"]]
|
||||
|
||||
def bd(t, ref, arm, metric, how, bytes_key="total_bytes"):
|
||||
return bd_rate(pts(t, ref, "total_bytes"), pts(t, ref, metric), pts(t, arm, bytes_key), pts(t, arm, metric), how)
|
||||
|
||||
for how, key in (("cubic", "bd"), ("linear", "bd_linear")):
|
||||
s[key] = {}
|
||||
for arm in ARMS + ("base8", "base10"):
|
||||
for ref in ("base10", "base8"):
|
||||
if arm == ref:
|
||||
continue
|
||||
for m in METRICS:
|
||||
s[key].setdefault(arm, {}).setdefault(ref, {})[m] = with_median({t: bd(t, ref, arm, m, how) for t in TITLES})
|
||||
s["bd_av1only"] = {arm: {ref: {m: with_median({t: bd(t, ref, arm, m, "cubic", "av1_bytes") for t in TITLES}) for m in METRICS}
|
||||
for ref in ("base10", "base8")} for arm in ARMS}
|
||||
s["linear_shift_psnr_max"] = max(abs(s["bd_linear"][arm][ref]["psnr"][t] - s["bd"][arm][ref]["psnr"][t])
|
||||
for arm in ARMS for ref in ("base10", "base8") for t in TITLES)
|
||||
s["basis_bytes"] = {arm: (K_OF[arm] + 1) * BASIS_DIMS * 2 for arm in ARMS}
|
||||
s["basis_only_psnr"] = {arm: {"min": min(c[t]["arms"][arm]["basis_only_psnr"] for t in TITLES),
|
||||
"max": max(c[t]["arms"][arm]["basis_only_psnr"] for t in TITLES)} for arm in ARMS}
|
||||
s["side_pct"] = {arm: {"min": min(p["side_bytes"] / p["total_bytes"] * 100 for t in TITLES for p in c[t]["arms"][arm]["points"]),
|
||||
"max": max(p["side_bytes"] / p["total_bytes"] * 100 for t in TITLES for p in c[t]["arms"][arm]["points"])}
|
||||
for arm in ARMS}
|
||||
s["clipped_max"] = max(c[t]["arms"][arm]["clipped_frac"] for t in TITLES for arm in ARMS)
|
||||
base8 = [p["total_bytes"] for t in TITLES for p in c[t]["arms"]["base8"]["points"]]
|
||||
s["base8_mb"] = {"min": min(base8) / 1e6, "max": max(base8) / 1e6}
|
||||
s["clip"] = {t: {"start_s": c[t]["clip_start_s"], "frames": c[t]["frames"]} for t in TITLES}
|
||||
|
||||
# Measurement (a): the rate proxy of the catalogue basis and of the per-title basis,
|
||||
# against the DCT's, at each target PSNR; and what the catalogue mean and the DC capture.
|
||||
s["proxy"] = {}
|
||||
for g in GRANULARITIES:
|
||||
for basis, key in (("catalogue", "catalogue"), ("per_title", "per_title")):
|
||||
for target in ("35", "40"):
|
||||
per = {t: (a[g][t][basis]["bpp_at_psnr"][target] / a[g][t]["dct"]["bpp_at_psnr"][target] - 1) * 100 for t in TITLES}
|
||||
s["proxy"].setdefault(g, {}).setdefault(key, {})[target] = with_median(per)
|
||||
fr = a["frame128x72"]
|
||||
s["mean_frame"] = {t: {"captured_ac_pct": fr[t]["catalogue"]["captured_ac"]["0"] * 100,
|
||||
"captured_incl_dc_pct": fr[t]["catalogue"]["captured_incl_dc"]["0"] * 100,
|
||||
"k16_incl_dc_pct": fr[t]["catalogue"]["captured_incl_dc"]["16"] * 100,
|
||||
"dct_k16_incl_dc_pct": fr[t]["dct"]["captured_incl_dc"]["16"] * 100} for t in TITLES}
|
||||
for k in ("captured_incl_dc_pct", "k16_incl_dc_pct"):
|
||||
s["mean_frame"][k] = {"min": min(s["mean_frame"][t][k] for t in TITLES), "max": max(s["mean_frame"][t][k] for t in TITLES)}
|
||||
|
||||
# Measurement (b): what recurs, from d_exact.json's runtime and byte shares and
|
||||
# b_recur.json's exact and 40 dB counts.
|
||||
s["recur"] = {}
|
||||
for t in TITLES:
|
||||
r = {}
|
||||
for part in ("flat", "cross35", "self_far35"):
|
||||
r[part] = {"runtime_pct": d[t][part]["runtime_pct"], "bytes_pct": d[t][part]["bytes_pct"], "frames": d[t][part]["frames"]}
|
||||
r["exact_nonflat"] = b[t]["exact_thumb_cross_nonflat"]
|
||||
r["full_ge40"] = b[t]["cross_ge35"]["full_psnr_ge40"]
|
||||
r["av1_mb"] = d[t]["av1_bytes_crf36"] / 1e6
|
||||
s["recur"][t] = r
|
||||
s["recur"]["cross35_runtime_pct_max"] = max(s["recur"][t]["cross35"]["runtime_pct"] for t in TITLES)
|
||||
s["recur"]["cross35_bytes_pct_max"] = max(s["recur"][t]["cross35"]["bytes_pct"] for t in TITLES)
|
||||
near = [d[t]["cross35"]["thumb_std_median"] for t in TITLES if d[t]["cross35"]["frames"] > 0]
|
||||
ordinary = [d[t]["nonflat_thumb_std_median"] for t in TITLES]
|
||||
s["recur"]["cross35_spread"] = {"min": min(near), "max": max(near)}
|
||||
s["recur"]["ordinary_spread"] = {"min": min(ordinary), "max": max(ordinary)}
|
||||
|
||||
# What the rule's figures come to: how far the GO bar is from cat64, what the GO bar
|
||||
# against base10 is against plain AV1, and how close the block-scale bases are.
|
||||
s["go_gap_points"] = {arm: min(s["bd"][arm]["base10"][m]["median"] for m in METRICS) - run_rule_go(rec) for arm in CATALOGUE_ARMS}
|
||||
s["go_vs_base8_pct"] = ((1 + run_rule_go(rec) / 100) * (1 + s["bd"]["base10"]["base8"]["psnr"]["median"] / 100) - 1) * 100
|
||||
blocks = ("block8", "block16", "block8_diff")
|
||||
s["proxy_block_median_abs_max"] = max(abs(s["proxy"][g]["catalogue"][x]["median"]) for g in blocks for x in ("35", "40"))
|
||||
s["proxy_block_title_abs_max"] = max(abs(s["proxy"][g]["catalogue"][x][t]) for g in ("block8", "block16") for x in ("35", "40") for t in TITLES)
|
||||
s["mean_frame"]["k16_vs_dct_points_max"] = max(abs(s["mean_frame"][t]["k16_incl_dc_pct"] - s["mean_frame"][t]["dct_k16_incl_dc_pct"]) for t in TITLES)
|
||||
return s
|
||||
|
||||
|
||||
def run_rule_go(rec):
|
||||
return rec["run.json"]["rule"]["go"]["bd_rate_max"]
|
||||
|
||||
|
||||
def verdicts(s, rule):
|
||||
"""The decision rule as run.json records it, applied to the computed values."""
|
||||
go = rule["go"]
|
||||
k1, k2, k3 = rule["k1"], rule["k2"], rule["k3"]
|
||||
go_met = any(sum(1 for t in TITLES if all(s["bd"][arm]["base10"][m][t] <= go["bd_rate_max"] for m in METRICS)) >= go["titles_min"]
|
||||
for arm in CATALOGUE_ARMS)
|
||||
k1_holds = all(not all(s["proxy"][g]["catalogue"][str(target)]["median"] <= -k1["below_dct_pct"] for target in k1["targets"])
|
||||
for g in GRANULARITIES)
|
||||
k2_holds = all(s["bd"][arm]["base10"][m]["median"] > k2["bd_rate_above"] for arm in CATALOGUE_ARMS for m in METRICS)
|
||||
k3_holds = all(s["recur"][t]["cross35"]["runtime_pct"] < k3["runtime_pct_below"] for t in TITLES)
|
||||
no_saving = all(s["bd"][arm][ref]["psnr"][t] > 0 for arm in ARMS for ref in ("base10", "base8") for t in TITLES)
|
||||
return {"go": go_met, "k1": k1_holds, "k2": k2_holds, "k3": k3_holds, "no_psnr_saving_any_title": no_saving}
|
||||
|
||||
|
||||
def lookup(s, path):
|
||||
v = s
|
||||
for part in path.split("."):
|
||||
if isinstance(v, dict) and part in v:
|
||||
v = v[part]
|
||||
else:
|
||||
return KeyError
|
||||
return v
|
||||
|
||||
|
||||
def check_text(s, files):
|
||||
marker = re.compile(r"([+\-−]?\d[\d,]*(?:\.\d+)?)\s*(%|dB|MB|B)?\**\s*<!--cat:([\w.\-/]+)-->")
|
||||
n = 0
|
||||
for f in files:
|
||||
text = open(f, encoding="utf-8").read()
|
||||
for m in marker.finditer(text):
|
||||
n += 1
|
||||
printed, path = m.group(1).replace("−", "-").replace(",", ""), m.group(3)
|
||||
v = lookup(s, path)
|
||||
if v is KeyError or not isinstance(v, (int, float)) or isinstance(v, bool):
|
||||
bad(f"{f}: <!--cat:{path}--> names no number")
|
||||
continue
|
||||
dec = len(printed.split(".")[1]) if "." in printed else 0
|
||||
want = round(v, dec)
|
||||
if abs(float(printed) - want) > 0.5 * 10 ** -dec + 1e-12 or (dec == 0 and float(printed) != round(v)):
|
||||
bad(f"{f}: prints {m.group(1)} for {path}, the readings give {v} ({want} at {dec} decimals)")
|
||||
every = len(re.findall(r"<!--cat:", text))
|
||||
found = len(marker.findall(text))
|
||||
if every != found:
|
||||
bad(f"{f}: {every - found} cat marker(s) do not directly follow a number")
|
||||
return n
|
||||
|
||||
|
||||
def main(argv):
|
||||
summary_only = len(argv) > 1 and argv[1] == "--summary"
|
||||
args = argv[2:] if summary_only else argv[1:]
|
||||
if not args:
|
||||
sys.exit(__doc__)
|
||||
d = args[0]
|
||||
run = json.load(open(os.path.join(d, "run.json"), encoding="utf-8"))
|
||||
if run.get("format") != "wai-catalogue-prior/1":
|
||||
sys.exit(f"not a catalogue-prior record: {run.get('format')!r}")
|
||||
rec = {}
|
||||
for name in READINGS:
|
||||
raw = open(os.path.join(d, name), "rb").read()
|
||||
if hashlib.sha256(raw).hexdigest() != run["readings"].get(name):
|
||||
bad(f"{name}: SHA-256 {hashlib.sha256(raw).hexdigest()} is not the one run.json states")
|
||||
rec[name] = json.loads(raw)
|
||||
if sorted(rec["c_rd.json"]) != sorted(TITLES):
|
||||
bad(f"c_rd.json: titles {sorted(rec['c_rd.json'])}, not {sorted(TITLES)}")
|
||||
rec["run.json"] = run
|
||||
s = summarise(rec)
|
||||
if summary_only:
|
||||
print(json.dumps(s, indent=1, sort_keys=True))
|
||||
return 0
|
||||
# The cubic BD-rates the run stored, recomputed from the points.
|
||||
for t in TITLES:
|
||||
for arm, got in rec["c_rd.json"][t]["bd_vs_base10"].items():
|
||||
for m in METRICS:
|
||||
mine = s["bd"][arm]["base10"][m][t]
|
||||
if got[m] is None or mine is None or abs(got[m] - mine) > 1e-6 * max(1.0, abs(mine)):
|
||||
bad(f"c_rd.json: {t} {arm} {m} BD-rate against base10 stored {got[m]}, recomputed {mine}")
|
||||
v = verdicts(s, run["rule"])
|
||||
for k, want in run["verdicts"].items():
|
||||
if v.get(k) != want:
|
||||
bad(f"run.json states verdict {k} = {want}; the readings give {v.get(k)}")
|
||||
quoted = check_text(s, args[1:])
|
||||
for f in FINDINGS:
|
||||
print(f)
|
||||
if FINDINGS:
|
||||
print(f"{len(FINDINGS)} finding(s)")
|
||||
return 1
|
||||
print(f"ok: 4 readings match run.json; {sum(1 for t in TITLES for _ in rec['c_rd.json'][t]['bd_vs_base10']) * 2} stored "
|
||||
f"BD-rates recomputed; verdicts {json.dumps(v, sort_keys=True)}; {quoted} quoted figure(s) match")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
```
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -1610,6 +1610,13 @@ name = "cli_conform"
|
|||
path = "tests/cli_conform.rs"
|
||||
required-features = ["quickstart", "score", "key_discovery"]
|
||||
|
||||
# The native sink through `wai conform`, its reference module and its C interface
|
||||
# (wai_native_sink_vectors), and the score decoder.
|
||||
[[test]]
|
||||
name = "cli_conform_sinks"
|
||||
path = "tests/cli_conform_sinks.rs"
|
||||
required-features = ["quickstart", "score", "native_sink"]
|
||||
|
||||
[[test]]
|
||||
name = "cli_fleet"
|
||||
path = "tests/cli_fleet.rs"
|
||||
|
|
@ -2160,6 +2167,11 @@ name = "neural_cost_limit"
|
|||
path = "tests/neural_cost_limit.rs"
|
||||
required-features = ["ffi", "neural", "video_int"]
|
||||
|
||||
[[test]]
|
||||
name = "jpegai_decode_path"
|
||||
path = "tests/jpegai_decode_path.rs"
|
||||
required-features = ["neural", "receipt_claims", "video_int"]
|
||||
|
||||
[[test]]
|
||||
name = "semantic_path"
|
||||
path = "tests/semantic_path.rs"
|
||||
|
|
|
|||
|
|
@ -228,6 +228,24 @@
|
|||
*/
|
||||
#define WAI_DECODE_STRICT 1
|
||||
|
||||
/**
|
||||
* The decode path of a [`WaiDecoded`]: the capability has one decode path
|
||||
* (`neural::DecodePath::Sole`). A session claim's decoder entry states `variant` `null`.
|
||||
*/
|
||||
#define WAI_DECODE_PATH_SOLE 1
|
||||
|
||||
/**
|
||||
* The decode path of a [`WaiDecoded`]: `wai.image.jpegai`'s exact decoder
|
||||
* (`neural::DecodePath::Exact`). A session claim's decoder entry states `variant` `exact`.
|
||||
*/
|
||||
#define WAI_DECODE_PATH_EXACT 2
|
||||
|
||||
/**
|
||||
* The decode path of a [`WaiDecoded`]: `wai.image.jpegai`'s float decode
|
||||
* (`neural::DecodePath::Float`). A session claim's decoder entry states `variant` `float`.
|
||||
*/
|
||||
#define WAI_DECODE_PATH_FLOAT 3
|
||||
|
||||
#define SECTION_CONTRACT 1
|
||||
|
||||
#define SECTION_GRAPH 2
|
||||
|
|
@ -606,10 +624,16 @@ typedef struct wai_int_declared_t {
|
|||
* `WAI_DECODED_IMAGE` (`data` holds RGB8, `width` and `height` set) or
|
||||
* `WAI_DECODED_VIDEO` (`data` holds the frames' RGB8 concatenated, `width`,
|
||||
* `height`, `n_frames` and `fps_x_1000` set). `index` is the `WAI2` rendition
|
||||
* picked (0 for a `WAI1` envelope). Free `data` with `wai_buffer_free`.
|
||||
* picked (0 for a `WAI1` envelope). `path` is the decode path that ran
|
||||
* (`WAI_DECODE_PATH_SOLE`, `WAI_DECODE_PATH_EXACT` or `WAI_DECODE_PATH_FLOAT`,
|
||||
* extensions/determinism-tiers.md §5), which a receipt of the decode states as its
|
||||
* decoder entry's `variant` (extensions/jwp-receipts.md §2.4.12). `fallback` is 1 when
|
||||
* a `WAI1` envelope decoded through its manifest's `fallback` capability and 0 when
|
||||
* through its `capability`; for a `WAI2` envelope `index` names the rendition, and with
|
||||
* it the capability, and `fallback` is 0. Free `data` with `wai_buffer_free`.
|
||||
* `wai_neural_decode_ex` writes the whole struct on every call that passes a
|
||||
* non-null `out`: on failure `kind` is 0 and `data` is empty, so `data` is always
|
||||
* safe to free.
|
||||
* non-null `out`: on failure `kind`, `path` and `fallback` are 0 and `data` is empty,
|
||||
* so `data` is always safe to free.
|
||||
*/
|
||||
typedef struct wai_decoded_t {
|
||||
uint32_t kind;
|
||||
|
|
@ -620,6 +644,8 @@ typedef struct wai_decoded_t {
|
|||
uint32_t sample_rate;
|
||||
uint32_t n_frames;
|
||||
uint32_t fps_x_1000;
|
||||
uint32_t path;
|
||||
uint32_t fallback;
|
||||
} wai_decoded_t;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -424,6 +424,10 @@ fn inspect(path: &str) -> Result<(), String> {
|
|||
Ok(()) => println!(" pin check: \u{2713} no static finding (SPEC \u{a7}3.1, \u{a7}7)"),
|
||||
Err(f) => println!(" pin check: \u{2717} {f}"),
|
||||
}
|
||||
// Informative: the envelope conforms without the pin (SPEC §3.1, Pin by default).
|
||||
if let Some(f) = w.check_unpinned() {
|
||||
println!(" note: {f}");
|
||||
}
|
||||
match w.refines() {
|
||||
None => {}
|
||||
Some(Ok(r)) => println!(" refines: base {}… layer {}{}", &r.base[..16], r.layer,
|
||||
|
|
@ -541,6 +545,9 @@ fn inspect(path: &str) -> Result<(), String> {
|
|||
Ok(()) => println!(" pin check: \u{2713} no static finding (SPEC \u{a7}3.1, \u{a7}7)"),
|
||||
Err((i, f)) => println!(" pin check: \u{2717} [{i}] {f}"),
|
||||
}
|
||||
for (i, f) in m.check_unpinned() {
|
||||
println!(" note: [{i}] {f}");
|
||||
}
|
||||
match m.check_component_roles() {
|
||||
Ok(()) => println!(" component roles: \u{2713} conform (SPEC \u{a7}5, \u{a7}7.1)"),
|
||||
Err(e) => println!(" component roles: \u{2717} {e}"),
|
||||
|
|
@ -598,9 +605,11 @@ fn license_str(cap: &str) -> &'static str {
|
|||
/// AC-4 frame — WAI carries it, the sink decodes it). The bytes survive
|
||||
/// byte-identically (§2): `wrap` then `unwrap` returns the exact input.
|
||||
///
|
||||
/// The envelope pins no prior. Its intent is `--intent` when given, else
|
||||
/// `replicate`, and `replicate` is written only where a sink accepts it unpinned
|
||||
/// (SPEC §7, `codecs::unpinned_envelope_intent`): over the standard classes. Over
|
||||
/// The envelope pins no prior, and where its capability decodes against a parameter
|
||||
/// set `wrap` says so (`prior_unpinned`, SPEC §3.1, *Pin by default*). Its intent is
|
||||
/// `--intent` when given, else `replicate`, and `replicate` is written only where a
|
||||
/// sink accepts it unpinned (SPEC §7, `codecs::unpinned_envelope_intent`): over the
|
||||
/// standard classes. Over
|
||||
/// any other capability (`wai.image.jpegai`, a float or integer-exact neural codec,
|
||||
/// a generator, a behavioral path) `wrap` refuses and writes nothing unless another
|
||||
/// intent is given.
|
||||
|
|
@ -685,11 +694,14 @@ fn wrap(cap: &str, input: &str, out: &str, fallback: Option<&str>,
|
|||
out::write(out, &bytes).map_err(|e| format!("{out}: {e}"))?;
|
||||
let n = bytes.len();
|
||||
let tier = wai::codecs::capability_tier(cap).map(|t| format!("{t:?}")).unwrap_or_else(|| "?".into());
|
||||
let mut lines = vec![
|
||||
format!("wrapped → {out} ({n} B envelope, {n_in} B payload)"),
|
||||
format!(" capability {cap} · media {} · {tier} · {} · intent {intent}", media_for(cap), license_str(cap)),
|
||||
];
|
||||
// `wrap` pins no prior: where SPEC §3.1 asks an encoder to pin one, it says so.
|
||||
lines.extend(envelope.check_unpinned().map(|f| format!(" note {f}")));
|
||||
Ok(Wrapped {
|
||||
lines: vec![
|
||||
format!("wrapped → {out} ({n} B envelope, {n_in} B payload)"),
|
||||
format!(" capability {cap} · media {} · {tier} · {} · intent {intent}", media_for(cap), license_str(cap)),
|
||||
],
|
||||
lines,
|
||||
facts: serde_json::json!({
|
||||
"output": out, "format": "WAI1", "envelope_bytes": n, "intent": intent, "media": media_for(cap),
|
||||
"components": [{"index": 0, "capability": cap, "role": "rendition", "bytes": n_in, "licence": license_str(cap), "tier": tier}],
|
||||
|
|
@ -801,6 +813,8 @@ fn wrap_with_companions(cap: &str, payload: Vec<u8>, out: &str, fallback: Option
|
|||
let mut lines = vec![format!("wrapped → {out} ({n} B WAI2 envelope, {} components, intent {intent})",
|
||||
envelope.renditions.len())];
|
||||
lines.extend(component_lines(&envelope));
|
||||
// `wrap` pins no prior: where SPEC §3.1 asks an encoder to pin one, it says so.
|
||||
lines.extend(envelope.check_unpinned().into_iter().map(|(i, f)| format!(" note [{i}] {f}")));
|
||||
let components: Vec<serde_json::Value> = envelope.manifest.renditions.iter().zip(envelope.renditions.iter()).enumerate()
|
||||
.map(|(i, (meta, p))| serde_json::json!({
|
||||
"index": i, "capability": meta.capability, "bytes": p.len(), "licence": license_str(&meta.capability),
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ fn cut(s: &str) -> String {
|
|||
|
||||
/// The `wai/` directory of a checkout: `--root`, else the first directory up from here
|
||||
/// that is one, or holds one.
|
||||
fn wai_root(given: Option<&str>) -> Result<PathBuf, OpError> {
|
||||
pub(crate) fn wai_root(given: Option<&str>) -> Result<PathBuf, OpError> {
|
||||
let is_root = |d: &Path| d.join("SPEC.md").is_file() && d.join("registry").join("capabilities.json").is_file();
|
||||
if let Some(g) = given {
|
||||
let d = PathBuf::from(g);
|
||||
|
|
@ -106,7 +106,7 @@ fn now_rfc3339() -> String {
|
|||
}
|
||||
|
||||
/// The corpus digest (conformance-results §2.1) of the files a run read.
|
||||
fn corpus_digest(root: &Path, files: &[PathBuf]) -> Result<String, OpError> {
|
||||
pub(crate) fn corpus_digest(root: &Path, files: &[PathBuf]) -> Result<String, OpError> {
|
||||
let mut read: Vec<(String, Vec<u8>)> = Vec::new();
|
||||
for f in files {
|
||||
let b = std::fs::read(f).map_err(|e| OpError::new(EXIT_NOT_FOUND, "corpus-file-missing", format!("{}: {e}", f.display()), "the corpus is incomplete; check out the standard again"))?;
|
||||
|
|
|
|||
|
|
@ -45,6 +45,41 @@ pub fn sign(record: Map<String, Value>, key: &SigningKey) -> Result<Map<String,
|
|||
conform_result::sign(record, key)
|
||||
}
|
||||
|
||||
/// A result record checked (§4), then held to §4.1 as a relying party that admits an
|
||||
/// implementation on it: the run passed (rule 2), its kind and suite are one this build runs
|
||||
/// and whose corpus `root` (a checkout's `wai/` directory) holds (rule 3), and its corpus
|
||||
/// digest is that corpus's (rule 4). Whether it is admitted, and a line for each thing
|
||||
/// checked.
|
||||
pub fn admit(bytes: &[u8], root: &std::path::Path) -> (bool, Vec<String>) {
|
||||
let (_, mut lines) = check(bytes);
|
||||
let Ok(a) = conform_result::check(bytes) else {
|
||||
return (false, lines);
|
||||
};
|
||||
let mut refuse = |rule: u8, code: &str, what: String| {
|
||||
lines.push(format!("FAIL admission rule {rule} {code}: {what}"));
|
||||
(false, std::mem::take(&mut lines))
|
||||
};
|
||||
if !a.conformant {
|
||||
return refuse(2, "not_conformant", format!("the run it records did not pass ({} of {} cases failed)", a.cases - a.passed, a.cases));
|
||||
}
|
||||
let Some(suite) = super::suites::SUITES.iter().find(|s| s.kind == a.kind && s.name == a.suite) else {
|
||||
return refuse(3, "suite_unknown", format!("{} {} is no suite this build runs", a.kind, a.suite));
|
||||
};
|
||||
let files = match (suite.load)(&root.join(suite.dir)) {
|
||||
Ok(l) => l.files,
|
||||
Err(e) => return refuse(3, "suite_unknown", format!("the corpus of {} {} does not read here: {e}", a.kind, a.suite)),
|
||||
};
|
||||
let here = match super::corpus_digest(root, &files) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return refuse(3, "suite_unknown", e.message),
|
||||
};
|
||||
if here != a.corpus_digest {
|
||||
return refuse(4, "corpus_mismatch", format!("it names the corpus {}, and the corpus of {} {} here is {here}", a.corpus_digest, a.kind, a.suite));
|
||||
}
|
||||
lines.push(format!("ok admitted: {} {} passed against the corpus of {}", a.kind, a.suite, root.display()));
|
||||
(true, lines)
|
||||
}
|
||||
|
||||
/// A result record checked (§4): whether it holds, and a line for each thing checked.
|
||||
pub fn check(bytes: &[u8]) -> (bool, Vec<String>) {
|
||||
match conform_result::check(bytes) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
//! | decoder | `score` | `score-conformance/` | a `WSCR` score | the rendered `i16le` PCM has the declared SHA-256 |
|
||||
//! | packager | `policy` | `pack-conformance/` | a policy file | it is admitted with the declared digest, or refused with the declared reason (packaging §3, §12) |
|
||||
//! | origin | `http` | `http-conformance/objects/` | an object | it is served as http-delivery §5.3 and §6.2 require: bytes, headers, ranges, head proof, preflight |
|
||||
//! | sink | `native-sink` | `live-conformance/native-sink/vectors.json` | one session, its answers withheld, or one configuration | the answers the sink writes, one a line in RFC 8785 form, have the SHA-256 of the answers the corpus states (native-sink §10) |
|
||||
|
||||
use serde_json::Value;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -66,7 +67,7 @@ pub struct Suite {
|
|||
pub load: fn(&Path) -> Result<Loaded, String>,
|
||||
}
|
||||
|
||||
pub const KINDS: &[&str] = &["verifier", "decoder", "packager", "origin"];
|
||||
pub const KINDS: &[&str] = &["verifier", "decoder", "packager", "origin", "sink"];
|
||||
|
||||
pub const SUITES: &[Suite] = &[
|
||||
Suite { kind: "verifier", name: "claim", dir: "claim-conformance", about: "typed claim receipts (jwp-receipts §3.1): accept with the link hash, or reject at a step with a reason", http: true, command: true, load: claim },
|
||||
|
|
@ -76,6 +77,7 @@ pub const SUITES: &[Suite] = &[
|
|||
Suite { kind: "decoder", name: "score", dir: "score-conformance", about: "a WSCR score rendered to its canonical i16le PCM (the score extension's mixdown-equivalence)", http: true, command: true, load: score },
|
||||
Suite { kind: "packager", name: "policy", dir: "pack-conformance", about: "packaging policies admitted with their digest, or refused with their reason (packaging §3, §12)", http: true, command: true, load: policy },
|
||||
Suite { kind: "origin", name: "http", dir: "http-conformance", about: "objects, head proofs, ranges and headers as an origin serves them (http-delivery §5.3, §6.2)", http: true, command: false, load: http_origin },
|
||||
Suite { kind: "sink", name: "native-sink", dir: "live-conformance/native-sink", about: "a native sink's answer to every call of a session, byte for byte, and its refusal of a configuration (native-sink §10)", http: true, command: true, load: native_sink },
|
||||
];
|
||||
|
||||
fn read_json(p: &Path) -> Result<Value, String> {
|
||||
|
|
@ -244,6 +246,52 @@ fn http_origin(dir: &Path) -> Result<Loaded, String> {
|
|||
Ok(Loaded { cases, files, corpus_file: None })
|
||||
}
|
||||
|
||||
/// The native sink's corpus (native-sink §10). Each case is given to the sink with its answers
|
||||
/// withheld: the bodies its calls name (`objects`, hexadecimal), its `config`, `seed` and
|
||||
/// `start_us`, and each call's `do`. Each configuration the corpus states a session refuses is a
|
||||
/// case of no call, under a seed of zeros. The sink opens the session and writes its answer to
|
||||
/// each call in order, or, refusing the configuration, that refusal alone (`{"error":
|
||||
/// <reason>}`): one JSON text a line, in RFC 8785 form. A case passes when what it writes has the
|
||||
/// SHA-256 of the answers the corpus states, written the same way.
|
||||
fn native_sink(dir: &Path) -> Result<Loaded, String> {
|
||||
let p = dir.join("vectors.json");
|
||||
let doc = read_json(&p)?;
|
||||
let objects = doc.get("objects").and_then(Value::as_object).ok_or_else(|| format!("{}: no `objects`", p.display()))?;
|
||||
let digest = |answers: &[&Value]| -> Result<String, String> {
|
||||
let mut text = String::new();
|
||||
for a in answers {
|
||||
text.push_str(&wai::jcs::jcs(a).map_err(|e| format!("{}: an answer outside RFC 8785's subset: {e}", p.display()))?);
|
||||
text.push('\n');
|
||||
}
|
||||
Ok(super::adapter::sha256_hex(text.as_bytes()))
|
||||
};
|
||||
let mut cases = Vec::new();
|
||||
for c in doc.get("cases").and_then(Value::as_array).ok_or_else(|| format!("{}: no `cases`", p.display()))? {
|
||||
let calls = c.get("calls").and_then(Value::as_array).ok_or_else(|| format!("{}: {} has no calls", p.display(), name_of(c)))?;
|
||||
let mut named = serde_json::Map::new();
|
||||
for d in calls.iter().map(|x| &x["do"]) {
|
||||
for k in ["body", "receipt"] {
|
||||
if let Some(n) = d.get(k).and_then(Value::as_str) {
|
||||
let b = objects.get(n).ok_or_else(|| format!("{}: {} names no object {n}", p.display(), name_of(c)))?;
|
||||
named.insert(n.to_string(), b.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
let input = serde_json::json!({
|
||||
"objects": named, "config": c["config"], "seed": c["seed"], "start_us": c["start_us"],
|
||||
"calls": calls.iter().map(|x| serde_json::json!({"do": x["do"]})).collect::<Vec<_>>(),
|
||||
});
|
||||
let answers: Vec<&Value> = calls.iter().map(|x| &x["answer"]).collect();
|
||||
cases.push(Case { name: name_of(c), input: Input::Json(input), expect: Expect::Output { sha256: digest(&answers)? } });
|
||||
}
|
||||
for (i, c) in doc.get("configs").and_then(Value::as_array).into_iter().flatten().enumerate() {
|
||||
let input = serde_json::json!({"objects": {}, "config": c["config"], "seed": "0".repeat(64), "start_us": 0, "calls": []});
|
||||
let refused = serde_json::json!({"error": c["refused"]});
|
||||
cases.push(Case { name: format!("configs/{i}"), input: Input::Json(input), expect: Expect::Output { sha256: digest(&[&refused])? } });
|
||||
}
|
||||
Ok(Loaded { cases, files: vec![p], corpus_file: None })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
//! (`wai/examples/`), or from an app made with Create (`--from app.oui`, `app.export`);
|
||||
//! - `dev.start` (`wai dev`): a relay, a room service and the room page on one origin, on this
|
||||
//! machine;
|
||||
//! - `receipt.verify` (`wai verify`): the receipts under a directory, conformance results and
|
||||
//! fleet report bundles, checked offline;
|
||||
//! - `receipt.verify` (`wai verify`): the receipts under a directory, conformance results,
|
||||
//! fleet report bundles and envelopes' pins, checked offline; with `--conformant`, each
|
||||
//! conformance result admits its implementation only for a run that passed against a
|
||||
//! checkout's corpus;
|
||||
//! - `compat.check` (`wai breaking`): what the working tree breaks against a git ref;
|
||||
//! - `conformance.run` (`wai conform`): WAI's conformance corpus run against another
|
||||
//! implementation, its result a signed record; `conformance.list` (`wai conformance list`,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,14 @@
|
|||
//!
|
||||
//! It also checks every conformance result record (`*.wai-conform.json`, written by `wai
|
||||
//! conform`) under each path: its signature, and that its summary and verdict are what its
|
||||
//! cases say.
|
||||
//! cases say. With `--conformant`, each record must also admit its implementation
|
||||
//! (conformance-results §4.1): the run passed, against the corpus of a checkout of the
|
||||
//! standard (`--root <wai-dir>`, or found from the current directory), named by its digest.
|
||||
//!
|
||||
//! And every envelope (`*.wai`) under each path: what a verifier finds about its pins
|
||||
//! without holding a parameter set (SPEC §3.1, §7), which makes it non-conforming, and the
|
||||
//! informative report `prior_unpinned` where it pins nothing that a capability it decodes
|
||||
//! through is registered as decoding against (SPEC §3.1, *Pin by default*), which does not.
|
||||
//!
|
||||
//! A fleet report's bundle (`*.wai-fleet.json`, extensions/fleet.md §7) under a path is verified
|
||||
//! too, in a build with `fleet`: the report recomputed from its enrolments and records.
|
||||
|
|
@ -26,17 +33,34 @@ pub fn main(_: &[String]) -> Result<bool, OpError> {
|
|||
pub fn main(args: &[String]) -> Result<bool, OpError> {
|
||||
use std::path::{Path, PathBuf};
|
||||
let usage = super::usage("receipt.verify");
|
||||
if let Some(a) = args.iter().find(|a| a.starts_with("--")) {
|
||||
return Err(OpError::usage(format!("wai verify: {a}: not an option ({usage})"), ""));
|
||||
let mut i = 0;
|
||||
let (mut conformant, mut root) = (false, None);
|
||||
while i < args.len() {
|
||||
match args[i].as_str() {
|
||||
"--conformant" => conformant = true,
|
||||
"--root" => {
|
||||
root = Some(args.get(i + 1).ok_or_else(|| OpError::usage(format!("wai verify: --root takes the wai/ directory of a checkout ({usage})"), ""))?.as_str());
|
||||
i += 1;
|
||||
}
|
||||
a if a.starts_with("--") => return Err(OpError::usage(format!("wai verify: {a}: not an option ({usage})"), "")),
|
||||
_ => {}
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
let paths = super::positional(args, &[]);
|
||||
if root.is_some() && !conformant {
|
||||
return Err(OpError::usage(format!("wai verify: --root names the corpus --conformant holds a record to ({usage})"), ""));
|
||||
}
|
||||
// Admission compares a record's corpus digest with the corpus of a checkout: without
|
||||
// one there is nothing to compare it with.
|
||||
let root = if conformant { Some(super::conform::wai_root(root)?) } else { None };
|
||||
let paths = super::positional(args, &["--root"]);
|
||||
if paths.is_empty() {
|
||||
return Err(OpError::usage(usage, ""));
|
||||
}
|
||||
fn walk(p: &Path, out: &mut Vec<PathBuf>, bundles: &mut Vec<PathBuf>, depth: usize) -> Result<(), String> {
|
||||
if p.is_file() {
|
||||
let n = p.to_string_lossy();
|
||||
if n.ends_with(".wai.receipts") || n.ends_with(super::conform::record::SUFFIX) {
|
||||
if n.ends_with(".wai.receipts") || n.ends_with(super::conform::record::SUFFIX) || n.ends_with(".wai") {
|
||||
out.push(p.to_path_buf());
|
||||
} else if p.to_string_lossy().ends_with(".wai-fleet.json") {
|
||||
bundles.push(p.to_path_buf());
|
||||
|
|
@ -65,17 +89,24 @@ pub fn main(args: &[String]) -> Result<bool, OpError> {
|
|||
}
|
||||
if indexes.is_empty() && bundles.is_empty() {
|
||||
return Err(OpError::from(format!(
|
||||
"no receipt index (*.wai.receipts), conformance result (*.wai-conform.json) or fleet bundle (*.wai-fleet.json) under {}",
|
||||
"no receipt index (*.wai.receipts), conformance result (*.wai-conform.json), fleet bundle (*.wai-fleet.json) or envelope (*.wai) under {}",
|
||||
paths.join(", ")
|
||||
)));
|
||||
}
|
||||
let mut all = true;
|
||||
let (mut n_ix, mut n_rec) = (0, 0);
|
||||
let (mut n_ix, mut n_rec, mut n_env) = (0, 0, 0);
|
||||
for ix in &indexes {
|
||||
println!("{}", ix.display());
|
||||
let (ok, lines) = if ix.to_string_lossy().ends_with(super::conform::record::SUFFIX) {
|
||||
n_rec += 1;
|
||||
super::conform::record::check(&std::fs::read(ix).map_err(|e| format!("{}: {e}", ix.display()))?)
|
||||
let bytes = std::fs::read(ix).map_err(|e| format!("{}: {e}", ix.display()))?;
|
||||
match root.as_deref() {
|
||||
Some(r) => super::conform::record::admit(&bytes, r),
|
||||
None => super::conform::record::check(&bytes),
|
||||
}
|
||||
} else if ix.to_string_lossy().ends_with(".wai") {
|
||||
n_env += 1;
|
||||
check_envelope(&std::fs::read(ix).map_err(|e| format!("{}: {e}", ix.display()))?)
|
||||
} else {
|
||||
n_ix += 1;
|
||||
check_index_file(ix)?
|
||||
|
|
@ -101,13 +132,68 @@ pub fn main(args: &[String]) -> Result<bool, OpError> {
|
|||
if n_rec > 0 {
|
||||
what.push(format!("{} conformance result{}", n_rec, if n_rec == 1 { "" } else { "s" }));
|
||||
}
|
||||
if n_env > 0 {
|
||||
what.push(format!("{} envelope{}", n_env, if n_env == 1 { "" } else { "s" }));
|
||||
}
|
||||
if m > 0 {
|
||||
what.push(format!("{m} fleet bundle{}", if m == 1 { "" } else { "s" }));
|
||||
}
|
||||
println!("wai verify: {} {}", what.join(" and "), if all { "verified" } else { "with a refusal (above)" });
|
||||
if let (Some(r), true, true) = (root.as_deref(), all, n_rec > 0) {
|
||||
println!("wai verify: every conformance result admits its implementation (conformance-results \u{a7}4.1), against the corpus of {}", r.display());
|
||||
}
|
||||
Ok(all)
|
||||
}
|
||||
|
||||
/// One envelope's pins, as a verifier holding no parameter set reads them (SPEC §3.1, §7):
|
||||
/// whether it conforms on that count — a static finding makes it non-conforming — and a line
|
||||
/// for each thing found, the informative `prior_unpinned` (SPEC §3.1, *Pin by default*)
|
||||
/// among them, which does not.
|
||||
#[cfg(feature = "quickstart")]
|
||||
pub(crate) fn check_envelope(bytes: &[u8]) -> (bool, Vec<String>) {
|
||||
use wai::container::{Wai, WaiMulti};
|
||||
let pin = |p: Option<&wai::container::Prior>| p.map(|p| format!("pinned {}", wai::pin::describe_pin(p))).unwrap_or_else(|| "no pin".into());
|
||||
match bytes.get(..4) {
|
||||
Some(b"WAI1") => match Wai::from_bytes(bytes) {
|
||||
Err(e) => (false, vec![format!("FAIL not an envelope that parses: {e}")]),
|
||||
Ok(w) => {
|
||||
let mut lines = Vec::new();
|
||||
let ok = match w.check_pin() {
|
||||
Ok(()) => {
|
||||
lines.push(format!("ok WAI1 {}, {}: no static finding on its pin (SPEC \u{a7}3.1, \u{a7}7)", w.capability(), pin(w.prior())));
|
||||
true
|
||||
}
|
||||
Err(f) => {
|
||||
lines.push(format!("FAIL WAI1 {}: {f}", w.capability()));
|
||||
false
|
||||
}
|
||||
};
|
||||
lines.extend(w.check_unpinned().map(|f| format!("note {f}")));
|
||||
(ok, lines)
|
||||
}
|
||||
},
|
||||
Some(b"WAI2") => match WaiMulti::from_bytes(bytes) {
|
||||
Err(e) => (false, vec![format!("FAIL not an envelope that parses: {e}")]),
|
||||
Ok(m) => {
|
||||
let mut lines = Vec::new();
|
||||
let ok = match m.check_pins() {
|
||||
Ok(()) => {
|
||||
lines.push(format!("ok WAI2, {} entries: no static finding on their pins (SPEC \u{a7}3.1, \u{a7}7)", m.manifest.renditions.len()));
|
||||
true
|
||||
}
|
||||
Err((i, f)) => {
|
||||
lines.push(format!("FAIL WAI2 entry [{i}] {}: {f}", m.manifest.renditions[i].capability));
|
||||
false
|
||||
}
|
||||
};
|
||||
lines.extend(m.check_unpinned().into_iter().map(|(i, f)| format!("note [{i}] {f}")));
|
||||
(ok, lines)
|
||||
}
|
||||
},
|
||||
_ => (false, vec!["FAIL not an envelope: it begins with neither WAI1 nor WAI2".into()]),
|
||||
}
|
||||
}
|
||||
|
||||
/// One fleet report bundle (extensions/fleet.md §7): whether it verified, and a line for its
|
||||
/// report and for each node, room and scope of energy.
|
||||
#[cfg(all(feature = "quickstart", feature = "fleet"))]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
//! wai_native_sink_vectors gen <corpus-dir> write vectors.json, from fixed seeds
|
||||
//! wai_native_sink_vectors verify <corpus-dir> vectors.json regenerates byte for byte,
|
||||
//! and every call gets the answer it states
|
||||
//! wai_native_sink_vectors answers [--c-abi] <case.json>
|
||||
//! the sink's answer to each call of one case
|
||||
//! as `wai conform sink --suite native-sink`
|
||||
//! gives it, its answers withheld: one JSON
|
||||
//! text a line, RFC 8785; through the Rust
|
||||
//! module, or with `--c-abi` through the C
|
||||
//! interface a native integration links
|
||||
//!
|
||||
//! The generator and this verifier are one implementation. The corpus's second
|
||||
//! implementation is `live-conformance/native_sink_verify.py`, written from the
|
||||
|
|
@ -14,6 +21,17 @@ use wai::native_sink::corpus;
|
|||
|
||||
fn main() {
|
||||
let a: Vec<String> = std::env::args().collect();
|
||||
if a.get(1).map(String::as_str) == Some("answers") {
|
||||
let (abi, file) = match (a.get(2).map(String::as_str), a.get(3)) {
|
||||
(Some("--c-abi"), Some(f)) => (true, f),
|
||||
(Some(f), None) if f != "--c-abi" => (false, &a[2]),
|
||||
_ => {
|
||||
eprintln!("usage: wai_native_sink_vectors answers [--c-abi] <case.json>");
|
||||
std::process::exit(2);
|
||||
}
|
||||
};
|
||||
std::process::exit(answers(Path::new(file), abi));
|
||||
}
|
||||
match (a.get(1).map(String::as_str), a.get(2)) {
|
||||
(Some("gen"), Some(d)) => {
|
||||
let p = Path::new(d).join("vectors.json");
|
||||
|
|
@ -25,7 +43,7 @@ fn main() {
|
|||
}
|
||||
(Some("verify"), Some(d)) => std::process::exit(verify(Path::new(d))),
|
||||
_ => {
|
||||
eprintln!("usage: wai_native_sink_vectors gen|verify <corpus-dir>");
|
||||
eprintln!("usage: wai_native_sink_vectors gen|verify <corpus-dir> | answers [--c-abi] <case.json>");
|
||||
std::process::exit(2);
|
||||
}
|
||||
}
|
||||
|
|
@ -52,3 +70,150 @@ fn verify(dir: &Path) -> i32 {
|
|||
fail += r.is_err() as i32;
|
||||
(fail != 0) as i32
|
||||
}
|
||||
|
||||
/// One case's answers, each on its own line in RFC 8785 form: the conformance runner hashes
|
||||
/// what is written and compares it with the answers the corpus states.
|
||||
fn answers(file: &Path, abi: bool) -> i32 {
|
||||
let case: serde_json::Value = match std::fs::read(file).map_err(|e| e.to_string()).and_then(|b| serde_json::from_slice(&b).map_err(|e| e.to_string())) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("{}: {e}", file.display());
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
let got = if abi { c_abi::answer_case(&case) } else { corpus::answer_case(&case) };
|
||||
match got.and_then(|v| v.iter().map(|a| wai::jcs::jcs(a).map_err(|e| e.to_string())).collect::<Result<Vec<_>, _>>()) {
|
||||
Ok(lines) => {
|
||||
for l in lines {
|
||||
println!("{l}");
|
||||
}
|
||||
0
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{}: {e}", file.display());
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The native sink through its C interface (`ffi_sink`, extensions/native-sink.md §9), as a
|
||||
/// native integration links it: every call made through the C symbols, and a refusal read
|
||||
/// from `wai_last_error`.
|
||||
mod c_abi {
|
||||
use serde_json::{json, Value};
|
||||
use std::ffi::{CStr, CString};
|
||||
use wai::ffi::{wai_buffer_free, wai_last_error, WaiBuffer, WAI_OK};
|
||||
use wai::ffi_sink::*;
|
||||
|
||||
/// A buffer's bytes, the buffer freed. An empty buffer may carry a null pointer, which
|
||||
/// is never read or freed.
|
||||
fn take(b: WaiBuffer) -> Vec<u8> {
|
||||
if b.data.is_null() {
|
||||
return Vec::new();
|
||||
}
|
||||
let v = if b.len == 0 { Vec::new() } else { unsafe { std::slice::from_raw_parts(b.data, b.len) }.to_vec() };
|
||||
unsafe { wai_buffer_free(b) };
|
||||
v
|
||||
}
|
||||
|
||||
fn empty() -> WaiBuffer {
|
||||
WaiBuffer { data: std::ptr::null_mut(), len: 0, cap: 0 }
|
||||
}
|
||||
|
||||
/// The reason of the last refusal: the code before the first colon.
|
||||
fn refused() -> Value {
|
||||
let s = unsafe { CStr::from_ptr(wai_last_error()) }.to_string_lossy().into_owned();
|
||||
json!({"error": s.split(':').next().unwrap_or_default()})
|
||||
}
|
||||
|
||||
fn c(s: &str) -> Result<CString, String> {
|
||||
CString::new(s).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn parsed(b: WaiBuffer) -> Result<Value, String> {
|
||||
serde_json::from_slice(&take(b)).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
pub fn answer_case(case: &Value) -> Result<Vec<Value>, String> {
|
||||
let obj = |n: &Value| -> Result<Option<Vec<u8>>, String> {
|
||||
match n.as_str() {
|
||||
None => Ok(None),
|
||||
Some(n) => case["objects"][n].as_str().and_then(wai::stream_audit::unhex).map(Some).ok_or_else(|| format!("no object {n}")),
|
||||
}
|
||||
};
|
||||
let cfg = c(&case["config"].to_string())?;
|
||||
let seed = case["seed"].as_str().and_then(wai::stream_audit::unhex).filter(|s| s.len() == 32).ok_or("seed: 32 bytes, hexadecimal")?;
|
||||
let start = case["start_us"].as_u64().ok_or("start_us: an integer")?;
|
||||
let mut s: *mut WaiSink = std::ptr::null_mut();
|
||||
if unsafe { wai_sink_open(cfg.as_ptr(), seed.as_ptr(), 32, start, &mut s) } != WAI_OK {
|
||||
return Ok(vec![refused()]);
|
||||
}
|
||||
// The session is freed however the case ends.
|
||||
struct Open(*mut WaiSink);
|
||||
impl Drop for Open {
|
||||
fn drop(&mut self) {
|
||||
unsafe { wai_sink_free(self.0) };
|
||||
}
|
||||
}
|
||||
let open = Open(s);
|
||||
let s = open.0;
|
||||
let mut out = Vec::new();
|
||||
for call in case["calls"].as_array().ok_or("no calls")? {
|
||||
let d = &call["do"];
|
||||
let text = |k: &str| d[k].as_str().ok_or_else(|| format!("{k}: a string")).and_then(c);
|
||||
let mut buf = empty();
|
||||
let got = match d["call"].as_str().unwrap_or("") {
|
||||
"playlist" => {
|
||||
let (url, b, r) = (text("url")?, obj(&d["body"])?.unwrap_or_default(), obj(&d["receipt"])?);
|
||||
let rp = r.as_ref().map_or(std::ptr::null(), |r| r.as_ptr());
|
||||
let st = unsafe { wai_sink_playlist(s, url.as_ptr(), b.as_ptr(), b.len(), rp, r.as_ref().map_or(0, Vec::len), &mut buf) };
|
||||
if st == WAI_OK { parsed(buf)? } else { refused() }
|
||||
}
|
||||
"parent" => {
|
||||
let (url, name, b) = (text("url")?, text("name")?, obj(&d["body"])?);
|
||||
let bp = b.as_ref().map_or(std::ptr::null(), |b| b.as_ptr());
|
||||
let st = unsafe { wai_sink_playlist_parent(s, url.as_ptr(), name.as_ptr(), bp, b.as_ref().map_or(0, Vec::len)) };
|
||||
if st == WAI_OK { json!({"ok": true}) } else { refused() }
|
||||
}
|
||||
"object" => {
|
||||
let (meta, b) = (c(&d["meta"].to_string())?, obj(&d["body"])?.unwrap_or_default());
|
||||
let st = unsafe { wai_sink_object(s, meta.as_ptr(), b.as_ptr(), b.len(), &mut buf) };
|
||||
if st == WAI_OK { parsed(buf)? } else { refused() }
|
||||
}
|
||||
"event" => {
|
||||
let n = text("name")?;
|
||||
let st = unsafe { wai_sink_event(s, n.as_ptr(), d["t_us"].as_u64().unwrap_or(0)) };
|
||||
if st == WAI_OK { json!({"ok": true}) } else { refused() }
|
||||
}
|
||||
"observe" => {
|
||||
let o = c(&d["obs"].to_string())?;
|
||||
let st = unsafe { wai_sink_observe(s, o.as_ptr(), &mut buf) };
|
||||
if st == WAI_OK { parsed(buf)? } else { refused() }
|
||||
}
|
||||
"energy" => {
|
||||
let e = c(&d["entry"].to_string())?;
|
||||
let st = unsafe { wai_sink_energy(s, e.as_ptr()) };
|
||||
if st == WAI_OK { json!({"ok": true}) } else { refused() }
|
||||
}
|
||||
"attach" => {
|
||||
let (n, b) = (text("name")?, obj(&d["body"])?.unwrap_or_default());
|
||||
let st = unsafe { wai_sink_attach(s, n.as_ptr(), b.as_ptr(), b.len()) };
|
||||
if st == WAI_OK { json!({"ok": true}) } else { refused() }
|
||||
}
|
||||
"cmcd" => {
|
||||
let st = unsafe { wai_sink_take_cmcd(s, &mut buf) };
|
||||
if st == WAI_OK { json!({"cmcd": String::from_utf8(take(buf)).map_err(|e| e.to_string())?}) } else { refused() }
|
||||
}
|
||||
"end" => {
|
||||
let n = text("ended")?;
|
||||
let st = unsafe { wai_sink_end(s, n.as_ptr(), d["t_us"].as_u64().unwrap_or(0), &mut buf) };
|
||||
if st == WAI_OK { json!({"bundle": String::from_utf8(take(buf)).map_err(|e| e.to_string())?}) } else { refused() }
|
||||
}
|
||||
other => return Err(format!("an unknown call {other}")),
|
||||
};
|
||||
out.push(got);
|
||||
}
|
||||
drop(open);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@
|
|||
//! first component-role finding.
|
||||
//! - `static.json`: the first finding a verifier makes without holding a parameter
|
||||
//! set ([`wai::container::Wai::check_pin`], [`wai::container::WaiMulti::check_pins`]).
|
||||
//! - `unpinned.json`: the informative report `prior_unpinned` (SPEC §3.1, *Pin by
|
||||
//! default*; [`wai::container::Wai::check_unpinned`],
|
||||
//! [`wai::container::WaiMulti::check_unpinned`]).
|
||||
//! - `listings/`: each `<name>.sha256` is the listing of `sets/<name>/`;
|
||||
//! `listings/bad/*` each break a listing rule (`set_malformed`).
|
||||
//!
|
||||
//! `pin-conformance/pin_verify.py` checks `cases.json` and `listings/` with code that
|
||||
//! shares nothing with this crate.
|
||||
//! `pin-conformance/pin_verify.py` checks `cases.json`, `unpinned.json` and `listings/`
|
||||
//! with code that shares nothing with this crate.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
|
@ -171,6 +174,22 @@ fn verify(dir: &Path) -> i32 {
|
|||
Err(e) => fail(e),
|
||||
}
|
||||
|
||||
// unpinned.json
|
||||
match read_json(&dir.join("unpinned.json")) {
|
||||
Ok(doc) => {
|
||||
for c in doc["cases"].as_array().into_iter().flatten() {
|
||||
checked += 1;
|
||||
let name = c["name"].as_str().unwrap_or("?");
|
||||
match run_unpinned(&c["manifest"]) {
|
||||
Ok(got) if got == c["expect"] => {}
|
||||
Ok(got) => fail(format!("unpinned.json {name}: expected {}, got {got}", c["expect"])),
|
||||
Err(e) => fail(format!("unpinned.json {name}: {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => fail(e),
|
||||
}
|
||||
|
||||
if failures.is_empty() {
|
||||
println!("pin-conformance: {checked} checks, all as declared");
|
||||
0
|
||||
|
|
@ -285,6 +304,25 @@ fn run_selection(dir: &Path, c: &Value) -> Result<Value, String> {
|
|||
Ok(Value::Object(out))
|
||||
}
|
||||
|
||||
/// The report `prior_unpinned` (SPEC §3.1, *Pin by default*): for a `WAI1` manifest,
|
||||
/// `prior_unpinned` or `none`; for a `WAI2` manifest, the index of every entry it is made
|
||||
/// about.
|
||||
fn run_unpinned(manifest: &Value) -> Result<Value, String> {
|
||||
match manifest["wai"].as_str() {
|
||||
Some("1.0") => {
|
||||
let m: Manifest = serde_json::from_value(manifest.clone()).map_err(|e| format!("manifest: {e}"))?;
|
||||
Ok(Wai::new(m, Vec::new()).check_unpinned().map_or("none", |f| f.code).into())
|
||||
}
|
||||
Some("1.1") => {
|
||||
let m: ManifestV2 = serde_json::from_value(manifest.clone()).map_err(|e| format!("manifest: {e}"))?;
|
||||
let n = m.renditions.len();
|
||||
let w = WaiMulti::from_parts(m, vec![Vec::new(); n]).map_err(|e| e.to_string())?;
|
||||
Ok(serde_json::json!(w.check_unpinned().iter().map(|(i, _)| *i).collect::<Vec<_>>()))
|
||||
}
|
||||
other => Err(format!("unknown wai version {other:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn run_static(manifest: &Value) -> Result<Value, String> {
|
||||
match manifest["wai"].as_str() {
|
||||
Some("1.0") => {
|
||||
|
|
|
|||
|
|
@ -288,6 +288,15 @@ fn specs() -> Vec<FixtureSpec> {
|
|||
layers: vec![l(Identity, 6, (0, 0), false), l(Identity, 1, (0, 0), false)],
|
||||
references: vec![("one layer, q 1", l(Identity, 1, (0, 0), false))],
|
||||
},
|
||||
FixtureSpec {
|
||||
name: "unpinned",
|
||||
about: "an unpinned wai.neural.int_hyper picture (16×16), decoded against the set the sink holds (SPEC §3.1, Pin by default), its decoder entry naming that set; an identity layer at q 6, then one at q 1; reference: one identity layer at q 1",
|
||||
case: "staged-conformance/cases/image_int_hyper_unpinned",
|
||||
cap: wai::codecs::CAP_IMAGE_INT_REFINE,
|
||||
media: "image",
|
||||
layers: vec![l(Identity, 6, (0, 0), false), l(Identity, 1, (0, 0), false)],
|
||||
references: vec![("one layer, q 1", l(Identity, 1, (0, 0), false))],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -406,11 +406,16 @@ pub const OPS: &[Op] = &[
|
|||
},
|
||||
Op {
|
||||
id: "receipt.verify",
|
||||
summary: "Every receipt index (*.wai.receipts) under each path, its group receipts and claims checked offline, and every conformance result and fleet report bundle there",
|
||||
summary: "Every receipt index (*.wai.receipts) under each path, its group receipts and claims checked offline, and every conformance result, fleet report bundle and envelope there",
|
||||
cli: Some("verify"),
|
||||
usage: "verify <path>... the receipts (receipt indexes *.wai.receipts), conformance results (*.wai-conform.json)
|
||||
and fleet report bundles (*.wai-fleet.json) under each path, checked offline",
|
||||
input: &[pos("paths", Ty::Strings, true, "Files or directories holding receipt indexes, conformance results or fleet report bundles.")],
|
||||
usage: "verify <path>... [--conformant [--root <wai-dir>]] the receipts (receipt indexes *.wai.receipts), conformance
|
||||
results (*.wai-conform.json), fleet report bundles (*.wai-fleet.json) and envelopes' pins (*.wai) under each path,
|
||||
checked offline; --conformant admits a result only for a run that passed against this checkout's corpus",
|
||||
input: &[
|
||||
pos("paths", Ty::Strings, true, "Files or directories holding receipt indexes, conformance results, fleet report bundles or envelopes."),
|
||||
switch("conformant", "--conformant", "Admit each conformance result only when its run passed, against the corpus of a checkout."),
|
||||
flag("root", "--root", Ty::Path, false, "With --conformant, the wai/ directory of the checkout whose corpus a result names."),
|
||||
],
|
||||
needs: Needs::All(&["quickstart"]),
|
||||
mcp: true,
|
||||
..OP
|
||||
|
|
@ -941,9 +946,9 @@ http vectors diff|fuzz <dir> --seed S --count N --out F",
|
|||
the kinds and suites; a record that exists is replaced only with --yes (--confirm, deprecated, is the same)",
|
||||
effect: Effect::WritesProject,
|
||||
input: &[
|
||||
pos("kind", Ty::String, true, "The kind of implementation (wai conformance list): a decoder, a verifier, an origin, a packager."),
|
||||
pos("kind", Ty::String, true, "The kind of implementation (wai conformance list): a decoder, a verifier, an origin, a packager, a sink."),
|
||||
flag("suite", "--suite", Ty::String, false, "The suite (wai conformance list)."),
|
||||
flag("impl", "--impl", Ty::String, true, "The implementation: a command with {in}/{out}/{corpus} placeholders, or a URL."),
|
||||
flag("impl", "--impl", Ty::String, true, "The implementation: a command with {case}/{in}/{out}/{corpus} placeholders, or a URL."),
|
||||
flag("key", "--key", Ty::Path, false, "The seed the record is signed with."),
|
||||
flag("out", "--out", Ty::Path, false, "The record (<suite>.wai-conform.json)."),
|
||||
flag("root", "--root", Ty::Path, false, "The wai/ directory whose corpora are run."),
|
||||
|
|
|
|||
|
|
@ -363,6 +363,13 @@ pub enum Reason {
|
|||
SessionDecoderMalformed,
|
||||
/// A decoder entry whose tier exceeds its capability's class tier.
|
||||
SessionTierExceedsClass,
|
||||
/// A decoder entry naming a `NeuralIntegerExact` capability with a decode path other
|
||||
/// than its integer one: a `variant` other than `null`, `exact` or the lowering the
|
||||
/// capability's registration names ([`session::DecoderEntry::exact_path`]).
|
||||
SessionVariantNotExact,
|
||||
/// A decoder entry whose `tier` exceeds what the path that ran holds: a path that is
|
||||
/// not exact, `float` among them, stating `decode-equivalence`.
|
||||
SessionTierExceedsVariant,
|
||||
/// A switch between equal paths or for an unlisted reason, switches out of order,
|
||||
/// more than 1024 written or more than their total.
|
||||
SessionSwitchInvalid,
|
||||
|
|
@ -423,6 +430,10 @@ pub enum Reason {
|
|||
/// A `stage` claim the session names counts units the session's outputs do not hold
|
||||
/// at its stages.
|
||||
SessionStageDisagrees,
|
||||
/// A `stage` claim the session names states a stage hash, a clip digest, over a base
|
||||
/// a decoder entry decoded at a weaker step than `decode-equivalence`, or over a base
|
||||
/// or layer an entry decoded by a path that is not exact.
|
||||
SessionStageHashNotExact,
|
||||
/// A `consumption` claim's `objects` are not in strictly ascending order.
|
||||
ConsumptionObjectsUnordered,
|
||||
/// A `consumption` claim's `subject` is not the first of its `objects`.
|
||||
|
|
@ -578,6 +589,8 @@ impl Reason {
|
|||
Reason::SessionHostMalformed => "session_host_malformed",
|
||||
Reason::SessionDecoderMalformed => "session_decoder_malformed",
|
||||
Reason::SessionTierExceedsClass => "session_tier_exceeds_class",
|
||||
Reason::SessionVariantNotExact => "session_variant_not_exact",
|
||||
Reason::SessionTierExceedsVariant => "session_tier_exceeds_variant",
|
||||
Reason::SessionSwitchInvalid => "session_switch_invalid",
|
||||
Reason::SessionMissedInvalid => "session_missed_invalid",
|
||||
Reason::SessionStageSetsInvalid => "session_stage_sets_invalid",
|
||||
|
|
@ -599,6 +612,7 @@ impl Reason {
|
|||
Reason::SessionUnitOutsideObject => "session_unit_outside_object",
|
||||
Reason::SessionClassicalMismatch => "session_classical_mismatch",
|
||||
Reason::SessionStageDisagrees => "session_stage_disagrees",
|
||||
Reason::SessionStageHashNotExact => "session_stage_hash_not_exact",
|
||||
Reason::ConsumptionObjectsUnordered => "consumption_objects_unordered",
|
||||
Reason::ConsumptionSubjectMismatch => "consumption_subject_mismatch",
|
||||
Reason::ConsumptionUsageUnordered => "consumption_usage_unordered",
|
||||
|
|
@ -4247,7 +4261,7 @@ pub(crate) fn rfc3339(s: &str) -> bool {
|
|||
/// fails instead of passing on nothing. (A function rather than a public constant so
|
||||
/// cbindgen does not export it into the C header.)
|
||||
pub fn corpus_vector_count() -> usize {
|
||||
636
|
||||
653
|
||||
}
|
||||
|
||||
/// The conformance corpus (`claim-conformance/vectors.json`), regenerated. Ed25519
|
||||
|
|
@ -5489,6 +5503,8 @@ mod corpus {
|
|||
v.extend(span_vectors());
|
||||
// §2.4.8's fabric record, appended the same way.
|
||||
v.extend(fabric_vectors());
|
||||
// §2.4.12's decode path (rules 13 and 14, and a stage hash), appended the same way.
|
||||
v.extend(session::decode_path_vectors());
|
||||
v
|
||||
}
|
||||
|
||||
|
|
@ -7500,6 +7516,19 @@ mod tests {
|
|||
assert_eq!(hex(digest.as_bytes()), "74f802d701daa0620b1577cb100a80951fedb797f9d47057efca61f21d40b8a6");
|
||||
}
|
||||
|
||||
/// The vectors the corpus held before a session's decode path was given its rules
|
||||
/// (all 636), pinned as the tests above pin theirs. The digest was computed from the
|
||||
/// committed corpus before those rules were added and must never be edited to make the
|
||||
/// test pass.
|
||||
#[test]
|
||||
fn the_vectors_before_the_decode_path_rules_are_unchanged() {
|
||||
let doc: Value = crate::oer::load(conformance_vectors().as_bytes()).unwrap();
|
||||
let vs = doc["vectors"].as_array().unwrap();
|
||||
let first = Value::Array(vs[..636].to_vec());
|
||||
let digest = blake3::hash(serde_json::to_string(&first).unwrap().as_bytes());
|
||||
assert_eq!(hex(digest.as_bytes()), "885e3ab74f416f9f8a0ef560dca930b31e430aa2b06a0d3bb126ab95fed841f0");
|
||||
}
|
||||
|
||||
/// A session claim's grammars (§2.4.12): `media`, a decoder identifier, and the
|
||||
/// rounding of its worker count.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -2720,3 +2720,244 @@ fn sealed_or_bad(s: SessionClaim) -> String {
|
|||
Err(e) => panic!("a step-6 vector's session must pass steps 1-4: {e}"),
|
||||
}
|
||||
}
|
||||
|
||||
// ───────── the decode path that ran (§2.4.12 rules 13 and 14, and a stage hash) ─────────
|
||||
|
||||
/// The object a one-picture session decoded from: not held as an envelope by any vector.
|
||||
fn picture() -> [u8; 32] {
|
||||
object_content_hash(b"WAI1 claim-conformance: a picture")
|
||||
}
|
||||
|
||||
/// A session of one picture through one decoder entry: `capability` decoded by `decoder`
|
||||
/// on the path `variant` names. `tier` is the declared tier of the pin the sink verified,
|
||||
/// or `none` for a decode that verified no pin, the entry naming the set it read either
|
||||
/// way; with no tier the entry names no set.
|
||||
fn one_picture(capability: &str, decoder: &str, module_status: Option<&str>, variant: Option<&str>, tier: Option<&str>) -> SessionClaim {
|
||||
let ps = [pres(0, 1, 0)];
|
||||
SessionClaim {
|
||||
media: "image".into(),
|
||||
decoders: vec![DecoderEntry {
|
||||
capability: capability.into(),
|
||||
rendition: None,
|
||||
object: picture(),
|
||||
first_unit: 0,
|
||||
path: SessionPath::Primary,
|
||||
decoder: decoder.into(),
|
||||
variant: variant.map(Into::into),
|
||||
pin: tier.map(|_| pin()),
|
||||
tier: tier.map(Into::into),
|
||||
module_status: module_status.map(Into::into),
|
||||
leaves: 1,
|
||||
}],
|
||||
units: 1,
|
||||
primary_units: 1,
|
||||
fallback_units: 0,
|
||||
switches: vec![],
|
||||
switches_total: 0,
|
||||
deadline_us: None,
|
||||
missed: 0,
|
||||
missed_units: vec![],
|
||||
stage_sets: vec![],
|
||||
counted_primary: 0,
|
||||
counted_fallback: 0,
|
||||
counted_discarded: 0,
|
||||
uncounted: 1,
|
||||
transferred: 367,
|
||||
classical_declared: None,
|
||||
classical_source: None,
|
||||
..over(model(), &ps)
|
||||
}
|
||||
}
|
||||
|
||||
/// The model session's stage claim, with its tier stated as `tier`: at
|
||||
/// `decode-equivalence` it carries a stage hash, the clip digest of its units.
|
||||
fn model_stage_at(tier: &str) -> ClaimReceipt {
|
||||
let ClaimBody::Stage(mut st) = model_stage_ok().body else { unreachable!("the model stage claim is a stage claim") };
|
||||
st.stage_hash = (tier == "decode-equivalence").then(|| object_content_hash(b"claim-conformance: a clip digest"));
|
||||
st.tier = tier.into();
|
||||
seal(&viewer(), SINK_ID, SubjectType::Object, entry(), 0, ClaimBody::Stage(st), None)
|
||||
}
|
||||
|
||||
/// The vectors of the decode path a decoder entry states (`variant`): rules 13 and 14 at
|
||||
/// step 4, and a stage hash against the entries that decoded its set at step 6. Appended
|
||||
/// after every earlier vector, which a test pins by digest.
|
||||
pub(super) fn decode_path_vectors() -> Vec<V> {
|
||||
const BUILTIN: &str = "builtin:[email protected]";
|
||||
let module = format!("module:sha256:{}", "5e".repeat(32));
|
||||
let picture_ok = |s: SessionClaim| sealed_about(picture(), s).to_json();
|
||||
let picture_bad = |s: SessionClaim| seal_bad(&viewer(), SINK_ID, SubjectType::Object, picture(), 0, ClaimBody::ReconstructSession(s));
|
||||
let mut v = vec![
|
||||
claim(
|
||||
"session_variant_float_accept",
|
||||
"a picture decoded by wai.image.jpegai's float path, stated as variant float: the path that ran, beside the capability, which is registered at entropy-consistency",
|
||||
picture_ok(one_picture("wai.image.jpegai", BUILTIN, None, Some("float"), None)),
|
||||
Ok(()),
|
||||
),
|
||||
claim(
|
||||
"session_variant_exact_accept",
|
||||
"the same picture decoded by wai.image.jpegai's exact decoder, stated as variant exact, under a pin of entropy-consistency",
|
||||
picture_ok(one_picture("wai.image.jpegai", BUILTIN, None, Some("exact"), Some("entropy-consistency"))),
|
||||
Ok(()),
|
||||
),
|
||||
claim(
|
||||
"session_variant_exact_integer_accept",
|
||||
"a NeuralIntegerExact capability whose entry states its integer path as variant exact, under a pin of decode-equivalence",
|
||||
picture_ok(one_picture("wai.neural.int_hyper", BUILTIN, None, Some("exact"), Some("decode-equivalence"))),
|
||||
Ok(()),
|
||||
),
|
||||
reject(
|
||||
"session_variant_not_exact",
|
||||
"a NeuralIntegerExact capability named with variant float: a decode of it in floating point is not a decode of it (determinism-tiers 3, rule 1)",
|
||||
picture_bad(one_picture("wai.neural.int_hyper", BUILTIN, None, Some("float"), Some("decode-equivalence"))),
|
||||
4,
|
||||
Reason::SessionVariantNotExact,
|
||||
),
|
||||
reject(
|
||||
"session_variant_not_exact_unpinned",
|
||||
"the same with no pin verified, its entry naming the set it read with the tier none, and a path named by a term this revision does not register: only null, exact or the capability's registered lowering names the integer path",
|
||||
picture_bad(one_picture("wai.neural.int_hyper", BUILTIN, None, Some("fp16"), Some("none"))),
|
||||
4,
|
||||
Reason::SessionVariantNotExact,
|
||||
),
|
||||
claim(
|
||||
"session_variant_lowering_accept",
|
||||
"a NeuralIntegerExact capability whose entry names its integer path by the lowering its registration names, as opus-dred 7.2's present entry names wai.audio.opus.dred.int's, dred-int/1, under a pin of decode-equivalence",
|
||||
sealed_about(picture(), SessionClaim { media: "audio".into(), ..one_picture("wai.audio.opus.dred.int", BUILTIN, None, Some("dred-int/1"), Some("decode-equivalence")) }).to_json(),
|
||||
Ok(()),
|
||||
),
|
||||
reject(
|
||||
"session_variant_lowering_of_another_capability",
|
||||
"wai.neural.int_hyper named with dred-int/1, the lowering another capability registers: not its integer path",
|
||||
picture_bad(one_picture("wai.neural.int_hyper", BUILTIN, None, Some("dred-int/1"), Some("decode-equivalence"))),
|
||||
4,
|
||||
Reason::SessionVariantNotExact,
|
||||
),
|
||||
reject(
|
||||
"session_tier_exceeds_variant",
|
||||
"a capability the registry does not classify, decoded by a module on its float path and stated under a pin of decode-equivalence: a float path's frames are not decode-equivalent, whatever the pin declares (determinism-tiers 3, rule 2)",
|
||||
picture_bad(one_picture("org.example.picture", &module, Some("pinned"), Some("float"), Some("decode-equivalence"))),
|
||||
4,
|
||||
Reason::SessionTierExceedsVariant,
|
||||
),
|
||||
reject(
|
||||
"session_tier_exceeds_variant_unregistered_term",
|
||||
"the same capability on a path named by a term this revision does not register, fp16, under a pin of decode-equivalence: for a capability the registry does not classify, only null or exact names an exact path",
|
||||
picture_bad(one_picture("org.example.picture", &module, Some("pinned"), Some("fp16"), Some("decode-equivalence"))),
|
||||
4,
|
||||
Reason::SessionTierExceedsVariant,
|
||||
),
|
||||
reject(
|
||||
"session_tier_exceeds_class_before_variant",
|
||||
"wai.image.jpegai's float path under a pin of decode-equivalence: the class rule, rule 6, refuses it first",
|
||||
picture_bad(one_picture("wai.image.jpegai", BUILTIN, None, Some("float"), Some("decode-equivalence"))),
|
||||
4,
|
||||
Reason::SessionTierExceedsClass,
|
||||
),
|
||||
reject(
|
||||
"session_tier_exceeds_class_inter_frame",
|
||||
"an inter-frame capability, wai.neural.dcvc_rt, under a pin of decode-equivalence: its registry class, neural-float, is the class of its weakest frame type, and rule 6 refuses a tier above it whatever path the entry states (determinism-tiers 3, rule 2)",
|
||||
seal_bad(&viewer(), SINK_ID, SubjectType::Object, picture(), 0, ClaimBody::ReconstructSession(SessionClaim {
|
||||
media: "video".into(),
|
||||
..one_picture("wai.neural.dcvc_rt", BUILTIN, None, None, Some("decode-equivalence"))
|
||||
})),
|
||||
4,
|
||||
Reason::SessionTierExceedsClass,
|
||||
),
|
||||
];
|
||||
|
||||
// ── step 6: a stage hash, the clip digest of a set's units ──
|
||||
let de_stage = model_stage_at("decode-equivalence");
|
||||
let named = |stage: &ClaimReceipt| SessionClaim { stage_sets: vec![StageSetRef { first_unit: 0, claim: stage.link_hash() }], ..model() };
|
||||
let held = |v: V, stage: &ClaimReceipt| with(v, "stage_claims", json!([stage.to_json()]));
|
||||
v.push(held(
|
||||
claim(
|
||||
"session_stage_hash_exact_accept",
|
||||
"the model session's stage set stated at decode-equivalence, with its stage hash: its base decoded only through a pinned integer capability, its layers through a residual refinement, every path exact",
|
||||
sealed(named(&de_stage)).to_json(),
|
||||
Ok(()),
|
||||
),
|
||||
&de_stage,
|
||||
));
|
||||
v.push(held(
|
||||
reject(
|
||||
"session_stage_hash_not_exact",
|
||||
"the same stage claim, while the base's unit 2, inside the set, was decoded through the base's fallback, wai.video.av1, a capability held to a tolerance, by an entry of its own naming the base: a clip digest over a sequence one of whose frames was decoded at a weaker tier (determinism-tiers 3, rule 3)",
|
||||
sealed_or_bad({
|
||||
let mut ps = model_presentations();
|
||||
ps[2] = pres(2, 1, 7);
|
||||
let mut s = over(named(&de_stage), &ps);
|
||||
s.decoders[0].leaves = 3;
|
||||
s.decoders.push(entry_decoder("wai.video.av1", SessionPath::Fallback, false, None, entry(), 0, 1));
|
||||
s.switches = vec![
|
||||
Switch { unit: 2, from: SessionPath::Primary, to: SessionPath::Fallback, reason: "deadline-threshold".into() },
|
||||
Switch { unit: 3, from: SessionPath::Fallback, to: SessionPath::Primary, reason: "recovered".into() },
|
||||
Switch { unit: 5, from: SessionPath::Primary, to: SessionPath::Fallback, reason: "set-incomplete".into() },
|
||||
];
|
||||
s.switches_total = 3;
|
||||
s
|
||||
}),
|
||||
6,
|
||||
Reason::SessionStageHashNotExact,
|
||||
),
|
||||
&de_stage,
|
||||
));
|
||||
v.push(held(
|
||||
reject(
|
||||
"session_stage_hash_not_exact_outside_the_set",
|
||||
"the same stage claim, while the fallback entry, wai.video.av1, names the base and presents only unit 5, outside the set: refused all the same, since the check is per object, whichever units an entry presented (determinism-tiers 3, rule 3)",
|
||||
sealed_or_bad({
|
||||
let mut s = named(&de_stage);
|
||||
s.decoders[2].object = entry();
|
||||
s.decoders[2].first_unit = 0;
|
||||
s
|
||||
}),
|
||||
6,
|
||||
Reason::SessionStageHashNotExact,
|
||||
),
|
||||
&de_stage,
|
||||
));
|
||||
v.push(held(
|
||||
reject(
|
||||
"session_stage_hash_over_a_float_layer",
|
||||
"the same stage claim, while one of the set's first layers is stated as decoded on a float path",
|
||||
sealed_or_bad({
|
||||
let mut s = named(&de_stage);
|
||||
s.decoders[3].variant = Some("float".into());
|
||||
s
|
||||
}),
|
||||
6,
|
||||
Reason::SessionStageHashNotExact,
|
||||
),
|
||||
&de_stage,
|
||||
));
|
||||
v.push(held(
|
||||
reject(
|
||||
"session_stage_hash_over_an_unpinned_base",
|
||||
"the same stage claim, while the base is stated as decoded with no pin verified, its entry naming the set it read with the tier none: its NeuralIntegerExact capability holds decode-equivalence only under a pin that declares it (staged-delivery 5)",
|
||||
sealed_or_bad({
|
||||
let mut s = named(&de_stage);
|
||||
s.decoders[0].tier = Some("none".into());
|
||||
s
|
||||
}),
|
||||
6,
|
||||
Reason::SessionStageHashNotExact,
|
||||
),
|
||||
&de_stage,
|
||||
));
|
||||
v.push(held(
|
||||
reject(
|
||||
"session_stage_hash_over_an_unpinned_base_earlier_text",
|
||||
"the same, with the base's entry as a sink of an earlier text of this extension writes an unpinned decode, pin and tier null, which a verifier cannot tell from a decode that read no set: refused all the same, since no pin declares decode-equivalence",
|
||||
sealed_or_bad({
|
||||
let mut s = named(&de_stage);
|
||||
s.decoders[0].pin = None;
|
||||
s.decoders[0].tier = None;
|
||||
s
|
||||
}),
|
||||
6,
|
||||
Reason::SessionStageHashNotExact,
|
||||
),
|
||||
&de_stage,
|
||||
));
|
||||
v
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,14 @@ pub const SESSION_MODULE_STATUSES: &[&str] = &["pinned", "fetched"];
|
|||
pub const BUILTIN_DECODER: &str = "builtin:";
|
||||
/// A decoder entry's `module:` prefix: a module's SHA-256 follows.
|
||||
pub const MODULE_DECODER: &str = "module:sha256:";
|
||||
/// The `variant` of a decoder entry whose path computes the reconstruction its capability
|
||||
/// registers (determinism-tiers §5): `wai.image.jpegai`'s exact decoder, or a
|
||||
/// `NeuralIntegerExact` capability's integer path, which may also be stated as `null` or
|
||||
/// by the lowering the capability's registration names ([`DecoderEntry::exact_path`]).
|
||||
pub const SESSION_VARIANT_EXACT: &str = "exact";
|
||||
/// The `variant` of a decoder entry whose path runs a stage in floating point that the
|
||||
/// capability's registration does not fix: `wai.image.jpegai`'s float decode.
|
||||
pub const SESSION_VARIANT_FLOAT: &str = "float";
|
||||
|
||||
/// The most switches a claim writes. Private, so the C header does not gain it.
|
||||
const MAX_SWITCHES: usize = 1024;
|
||||
|
|
@ -134,10 +142,18 @@ pub struct DecoderEntry {
|
|||
pub path: SessionPath,
|
||||
/// `builtin:<crate>@<version>` or `module:sha256:<hex>`.
|
||||
pub decoder: String,
|
||||
/// The decode path that ran: [`SESSION_VARIANT_EXACT`] or [`SESSION_VARIANT_FLOAT`]
|
||||
/// (`neural::DecodePath::variant`), or `None` for a capability with one path. A
|
||||
/// `NeuralIntegerExact` capability's integer path may also be named by the lowering its
|
||||
/// registration names, as opus-dred §7.2 names `dred-int/1`.
|
||||
pub variant: Option<String>,
|
||||
/// The SHA-256 of the pin the sink verified.
|
||||
/// The parameter set the decode read, by the digest a pin over it names (SPEC §3.1,
|
||||
/// *Pin by default*): the SHA-256 of the pin the sink verified, or, for a decode that
|
||||
/// verified none, the digest a pin over the bytes it read would name. `None` when the
|
||||
/// decode read no parameter set.
|
||||
pub pin: Option<[u8; 32]>,
|
||||
/// That pin's declared tier, as SPEC §3.1 reads it.
|
||||
/// That pin's declared tier, as SPEC §3.1 reads it; `none` for a decode that verified
|
||||
/// no pin.
|
||||
pub tier: Option<String>,
|
||||
/// For a `module:` decoder, the anchor its grant was confirmed under.
|
||||
pub module_status: Option<String>,
|
||||
|
|
@ -148,6 +164,17 @@ pub struct DecoderEntry {
|
|||
type EntryKey<'a> = (&'a str, Option<u64>, [u8; 32], u64, SessionPath, &'a str, Option<&'a str>, Option<[u8; 32]>, Option<&'a str>, Option<&'a str>);
|
||||
|
||||
impl DecoderEntry {
|
||||
/// Whether the entry states an exact path (§2.4.12, `variant`): `null`, `exact`, or, for a
|
||||
/// capability of the registry class `NeuralIntegerExact`, the lowering of the operator set
|
||||
/// its registration names (its `ops` member's `lowering`, such as `dred-int/1`). Any other
|
||||
/// term, `float` among them, names a path that is not exact.
|
||||
pub fn exact_path(&self) -> bool {
|
||||
let Some(v) = self.variant.as_deref() else { return true };
|
||||
v == SESSION_VARIANT_EXACT
|
||||
|| (crate::codecs::capability_replicate_class(&self.capability) == Some(crate::codecs::ReplicateClass::NeuralIntegerExact)
|
||||
&& crate::codecs::capability_ops(&self.capability).and_then(|o| o.lowering) == Some(v))
|
||||
}
|
||||
|
||||
/// Everything but `leaves`: two entries with one key are one entry.
|
||||
fn key(&self) -> EntryKey<'_> {
|
||||
(
|
||||
|
|
@ -719,6 +746,19 @@ impl SessionClaim {
|
|||
}
|
||||
e.acquisition_class()?;
|
||||
}
|
||||
// Rule 13: a NeuralIntegerExact capability is decoded by its integer path only; a
|
||||
// decode by any other path is not a decode of it (determinism-tiers §3, *No tier
|
||||
// between the two*, rule 1).
|
||||
let integer = |cap: &str| crate::codecs::capability_replicate_class(cap) == Some(crate::codecs::ReplicateClass::NeuralIntegerExact);
|
||||
if self.decoders.iter().any(|d| integer(&d.capability) && !d.exact_path()) {
|
||||
return Err(Reason::SessionVariantNotExact);
|
||||
}
|
||||
// Rule 14: no pin's tier above what the path that ran holds; frames a path that is not
|
||||
// exact decoded are not decode-equivalent, whatever the pin declares (rule 2 there).
|
||||
let de = Some(crate::codecs::DeclaredTier::DecodeEquivalence);
|
||||
if self.decoders.iter().any(|d| !d.exact_path() && d.tier.as_deref().and_then(crate::codecs::DeclaredTier::from_token) == de) {
|
||||
return Err(Reason::SessionTierExceedsVariant);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -1330,7 +1370,11 @@ impl ClaimReceipt {
|
|||
/// layer object that arrived are among them (`session_stage_disagrees`). Each decoder
|
||||
/// entry whose object is a claim's base, or a layer object a claim lists, has a
|
||||
/// `first_unit` at which `stage_sets` names a claim of that object
|
||||
/// (`session_first_unit_mismatch`). Then, with the
|
||||
/// (`session_first_unit_mismatch`). A claim that states a stage hash has every entry
|
||||
/// naming its base at `decode-equivalence` by staged-delivery §5's base step, and no
|
||||
/// entry naming its base or a layer it lists with a path that is not exact
|
||||
/// ([`DecoderEntry::exact_path`]), whichever units the entry presented
|
||||
/// (`session_stage_hash_not_exact`). Then, with the
|
||||
/// presentations (`session_stage_disagrees`): for each claim and each entry naming it,
|
||||
/// every unit of its set is presented, and for each stage `s` from 1 up the units of
|
||||
/// the set presented at more than `s` layers are no fewer than it counts at `s` or
|
||||
|
|
@ -1376,6 +1420,31 @@ impl ClaimReceipt {
|
|||
if s.decoders.iter().any(|d| at.get(&d.object).is_some_and(|fs| !fs.contains(&d.first_unit))) {
|
||||
return Err(Rejection::at(6, Reason::SessionFirstUnitMismatch));
|
||||
}
|
||||
// A stage hash is a clip digest over the set's units (staged-delivery §5): every
|
||||
// entry naming the set's base decodes it at decode-equivalence by the base step of
|
||||
// staged-delivery §5, and no entry naming the base or a layer the claim lists ran a
|
||||
// path that is not exact (determinism-tiers §3, *No tier between the two*, rule 3).
|
||||
// The check is per object, whichever units an entry presented, so that its outcome
|
||||
// does not depend on whether the verifier holds the outputs.
|
||||
let base_exact = |d: &DecoderEntry| match crate::codecs::capability_replicate_class(&d.capability) {
|
||||
Some(crate::codecs::ReplicateClass::StandardDefined) => true,
|
||||
Some(crate::codecs::ReplicateClass::NeuralIntegerExact) => d.tier.as_deref() == Some("decode-equivalence"),
|
||||
_ => false,
|
||||
};
|
||||
for (c, _) in &claims {
|
||||
let ClaimBody::Stage(st) = &c.body else { continue };
|
||||
if st.stage_hash.is_none() {
|
||||
continue;
|
||||
}
|
||||
let mixed = s.decoders.iter().any(|d| {
|
||||
let base = d.object == c.subject;
|
||||
let layer = st.objects.iter().any(|o| o.object == d.object);
|
||||
(base && !base_exact(d)) || ((base || layer) && !d.exact_path())
|
||||
});
|
||||
if mixed {
|
||||
return Err(Rejection::at(6, Reason::SessionStageHashNotExact));
|
||||
}
|
||||
}
|
||||
let Some(ps) = outputs else { return Ok(()) };
|
||||
let finals = SessionClaim::finals(ps);
|
||||
// (set-local unit, largest layers) of every unit presented in [f, f + n).
|
||||
|
|
|
|||
|
|
@ -1649,6 +1649,17 @@ pub fn decodes_against_parameter_set(cap: &str) -> bool {
|
|||
)
|
||||
}
|
||||
|
||||
/// Whether an encoder SHOULD pin the parameter set a payload of `cap` was coded against
|
||||
/// (SPEC §3.1, *Pin by default*): `cap` is registered as decoding against one
|
||||
/// ([`decodes_against_parameter_set`]), and its payload does not name its own set, as the
|
||||
/// payload of `wai.generate.spectral` names its model. `false` for an unregistered
|
||||
/// capability: this revision does not know what it decodes against. An envelope that pins
|
||||
/// nothing where this holds still conforms in `1.x`, and a verifier reports it
|
||||
/// (`prior_unpinned`).
|
||||
pub fn pin_by_default(cap: &str) -> bool {
|
||||
decodes_against_parameter_set(cap) && cap != CAP_GENERATE_SPECTRAL
|
||||
}
|
||||
|
||||
/// The registered shape of a capability's parameter set (SPEC §5): one file, or
|
||||
/// several. A form-less pin is admissible only over a one-file set (SPEC §3.1,
|
||||
/// `form_required`); a set of several files is pinned through its listing
|
||||
|
|
|
|||
|
|
@ -697,7 +697,8 @@ pub fn replicate_is_sound(intent: &str, prior: Option<&Prior>) -> bool {
|
|||
pub struct PinFinding {
|
||||
/// The reason: `prior_not_applicable`, `pin_malformed`, `tier_unregistered`,
|
||||
/// `supersedes_malformed`, `form_not_registered`, `form_required`,
|
||||
/// `tier_exceeds_class`, or a `replicate_*` reason.
|
||||
/// `tier_exceeds_class`, or a `replicate_*` reason; or, from `check_unpinned`, the
|
||||
/// informative report `prior_unpinned` ([`PRIOR_UNPINNED`]).
|
||||
pub code: &'static str,
|
||||
pub detail: String,
|
||||
}
|
||||
|
|
@ -722,6 +723,28 @@ pub const PIN_FINDING_CODES: &[&str] = &[
|
|||
"replicate_form_not_exact",
|
||||
];
|
||||
|
||||
/// The report a verifier makes about an envelope that pins no parameter set where a
|
||||
/// capability it decodes through is registered as decoding against one (SPEC §3.1, *Pin by
|
||||
/// default*; [`Wai::check_unpinned`], [`WaiMulti::check_unpinned`]). It is informative:
|
||||
/// such an envelope conforms in `1.x`, so the code is not one of [`PIN_FINDING_CODES`].
|
||||
pub const PRIOR_UNPINNED: &str = "prior_unpinned";
|
||||
|
||||
/// [`PRIOR_UNPINNED`] for a pin `prior` over `caps`: the first capability of `caps` whose
|
||||
/// set an encoder SHOULD pin (`codecs::pin_by_default`), when `prior` is absent.
|
||||
fn unpinned_report(caps: &[&str], prior: Option<&Prior>) -> Option<PinFinding> {
|
||||
if prior.is_some() {
|
||||
return None;
|
||||
}
|
||||
let c = caps.iter().find(|c| crate::codecs::pin_by_default(c))?;
|
||||
Some(PinFinding {
|
||||
code: PRIOR_UNPINNED,
|
||||
detail: format!(
|
||||
"{c} decodes against a parameter set and the envelope pins none, so a sink decodes it against \
|
||||
whatever set it holds; an encoder SHOULD pin the set the payload was coded against (SPEC \u{a7}3.1)"
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
/// The static pin findings for a pin that would apply to the capabilities `caps`
|
||||
/// (the first is the one decoded through first), under `intent`.
|
||||
fn static_pin_findings(caps: &[&str], prior: Option<&Prior>, intent: &str) -> Result<(), PinFinding> {
|
||||
|
|
@ -1441,6 +1464,14 @@ impl Wai {
|
|||
static_pin_findings(&caps, self.prior(), self.intent())
|
||||
}
|
||||
|
||||
/// [`PRIOR_UNPINNED`] when this envelope pins no parameter set and its capability, or
|
||||
/// else its fallback, is one whose set an encoder SHOULD pin (SPEC §3.1, *Pin by
|
||||
/// default*). Informative: it is reported whatever [`Self::check_pin`] finds.
|
||||
pub fn check_unpinned(&self) -> Option<PinFinding> {
|
||||
let caps: Vec<&str> = [Some(self.capability()), self.fallback()].into_iter().flatten().collect();
|
||||
unpinned_report(&caps, self.prior())
|
||||
}
|
||||
|
||||
pub fn media(&self) -> &str {
|
||||
&self.manifest.media
|
||||
}
|
||||
|
|
@ -3162,6 +3193,18 @@ impl WaiMulti {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// [`PRIOR_UNPINNED`] for each entry, with its index, that pins no parameter set while
|
||||
/// its capability is one whose set an encoder SHOULD pin (SPEC §3.1, *Pin by default*),
|
||||
/// in table order. Informative, as [`Wai::check_unpinned`].
|
||||
pub fn check_unpinned(&self) -> Vec<(usize, PinFinding)> {
|
||||
self.manifest
|
||||
.renditions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, m)| unpinned_report(&[m.capability.as_str()], m.prior.as_ref()).map(|f| (i, f)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
/// The refinements of the entry at `index`, in table order: each entry with
|
||||
/// `role: "refinement"` whose `refines.base` names that entry's `id`, and whose
|
||||
|
|
@ -4717,6 +4760,50 @@ pub(crate) mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// SPEC §3.1, *Pin by default*: an envelope that pins nothing where its capability, or
|
||||
/// its fallback, decodes against a parameter set is reported `prior_unpinned`, whatever
|
||||
/// its intent and whatever else is found; a pinned one, one over a capability that reads
|
||||
/// no set, the generator whose payload names its model, and an unregistered capability
|
||||
/// are not.
|
||||
#[test]
|
||||
fn an_unpinned_envelope_is_reported_where_it_decodes_against_a_set() {
|
||||
let on = |cap: &str, fb: Option<&str>, intent: &str, p: Option<Prior>| {
|
||||
let mut c = Conditioning::new("k");
|
||||
c.prior = p;
|
||||
let r = match fb {
|
||||
Some(f) => ModelRequirement::new(cap).with_fallback(f),
|
||||
None => ModelRequirement::new(cap),
|
||||
};
|
||||
Wai::new(Manifest::new("image", intent, r, c), vec![])
|
||||
};
|
||||
let report = |w: &Wai| w.check_unpinned().map(|f| f.code);
|
||||
let pin = Prior::new("m", MODEL_SHA, "int");
|
||||
for intent in ["improve", "create", "replicate"] {
|
||||
assert_eq!(report(&on("wai.neural.int_mlicpp", None, intent, None)), Some(PRIOR_UNPINNED), "{intent}");
|
||||
assert_eq!(report(&on("wai.neural.bmshj2018", None, intent, None)), Some(PRIOR_UNPINNED), "{intent}");
|
||||
}
|
||||
// Reported beside the finding that makes the envelope non-conforming.
|
||||
let unsound = on("wai.neural.int_synth", None, "replicate", None);
|
||||
assert_eq!(unsound.check_pin().err().map(|f| f.code), Some("replicate_unpinned"));
|
||||
assert_eq!(report(&unsound), Some(PRIOR_UNPINNED));
|
||||
assert_eq!(report(&on("wai.neural.int_mlicpp", None, "improve", Some(pin.clone()))), None);
|
||||
assert_eq!(report(&on("wai.image.png", None, "replicate", None)), None);
|
||||
assert_eq!(report(&on("wai.image.png", Some("wai.neural.bmshj2018"), "improve", None)), Some(PRIOR_UNPINNED),
|
||||
"the fallback decodes the same payload against a set");
|
||||
assert_eq!(report(&on("wai.generate.spectral", None, "create", None)), None, "its payload names its model");
|
||||
assert_eq!(report(&on("wai.neural.future", None, "improve", None)), None, "unregistered: its set is not known");
|
||||
assert!(!PIN_FINDING_CODES.contains(&PRIOR_UNPINNED), "informative, not a finding of non-conformance");
|
||||
|
||||
let m = WaiMulti::new("image", "improve", Value::Null, vec![
|
||||
Rendition::media("wai.image.png", "png", b"PNG".to_vec()),
|
||||
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", b"M".to_vec()),
|
||||
Rendition::media("wai.neural.int_mlicpp", "int_mlicpp", b"N".to_vec()).with_prior(pin),
|
||||
Rendition::media("wai.neural.bmshj2018", "bmshj2018", b"B".to_vec()),
|
||||
]);
|
||||
let entries: Vec<(usize, &str)> = m.check_unpinned().iter().map(|(i, f)| (*i, f.code)).collect();
|
||||
assert_eq!(entries, [(1, PRIOR_UNPINNED), (3, PRIOR_UNPINNED)]);
|
||||
}
|
||||
|
||||
// ---- component identity (SPEC §7.1) ----
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ use std::slice;
|
|||
use crate::container::{Wai, WaiMulti};
|
||||
use crate::ffi::{set_err, WaiBuffer, WAI_ERR_CODEC, WAI_ERR_CONTAINER, WAI_ERR_INVALID_ARG,
|
||||
WAI_ERR_NO_RENDITION, WAI_ERR_PANIC, WAI_ERR_PRIOR, WAI_ERR_UNSOUND_REPLICATE, WAI_OK};
|
||||
use crate::neural::{decode_envelope, decode_envelope_strict, decode_multi, decode_multi_strict, decodes_to_int,
|
||||
use crate::neural::{decode_envelope, decode_envelope_strict, decode_multi, decode_multi_strict, decodes_to_int, DecodePath,
|
||||
DecodeError, Decoded, ModelRegistry};
|
||||
|
||||
unsafe fn input_envelope<'a>(bytes: *const u8, len: usize) -> Option<&'a [u8]> {
|
||||
|
|
@ -93,7 +93,7 @@ fn decode_one(envelope: *const u8, envelope_len: usize, model_path: *const c_cha
|
|||
return Err(no_kind(&[env.capability()]));
|
||||
}
|
||||
let reg = ModelRegistry::new().register(env.capability().to_string(), Path::new(path));
|
||||
decode_envelope(&env, ®).map_err(decode_status)
|
||||
decode_envelope(&env, ®).map(|d| d.output).map_err(decode_status)
|
||||
}
|
||||
|
||||
fn wrong_media(want: &str) -> (c_int, String) {
|
||||
|
|
@ -192,16 +192,32 @@ pub const WAI_DECODED_VIDEO: u32 = 3;
|
|||
/// `wai_neural_decode_ex` flag: apply SPEC §7's replicate rule (strict).
|
||||
pub const WAI_DECODE_STRICT: u32 = 1;
|
||||
|
||||
/// The decode path of a [`WaiDecoded`]: the capability has one decode path
|
||||
/// (`neural::DecodePath::Sole`). A session claim's decoder entry states `variant` `null`.
|
||||
pub const WAI_DECODE_PATH_SOLE: u32 = 1;
|
||||
/// The decode path of a [`WaiDecoded`]: `wai.image.jpegai`'s exact decoder
|
||||
/// (`neural::DecodePath::Exact`). A session claim's decoder entry states `variant` `exact`.
|
||||
pub const WAI_DECODE_PATH_EXACT: u32 = 2;
|
||||
/// The decode path of a [`WaiDecoded`]: `wai.image.jpegai`'s float decode
|
||||
/// (`neural::DecodePath::Float`). A session claim's decoder entry states `variant` `float`.
|
||||
pub const WAI_DECODE_PATH_FLOAT: u32 = 3;
|
||||
|
||||
/// What `wai_neural_decode_ex` decoded.
|
||||
///
|
||||
/// `kind` is `WAI_DECODED_AUDIO` (`data` holds `f32` samples, `sample_rate` set),
|
||||
/// `WAI_DECODED_IMAGE` (`data` holds RGB8, `width` and `height` set) or
|
||||
/// `WAI_DECODED_VIDEO` (`data` holds the frames' RGB8 concatenated, `width`,
|
||||
/// `height`, `n_frames` and `fps_x_1000` set). `index` is the `WAI2` rendition
|
||||
/// picked (0 for a `WAI1` envelope). Free `data` with `wai_buffer_free`.
|
||||
/// picked (0 for a `WAI1` envelope). `path` is the decode path that ran
|
||||
/// (`WAI_DECODE_PATH_SOLE`, `WAI_DECODE_PATH_EXACT` or `WAI_DECODE_PATH_FLOAT`,
|
||||
/// extensions/determinism-tiers.md §5), which a receipt of the decode states as its
|
||||
/// decoder entry's `variant` (extensions/jwp-receipts.md §2.4.12). `fallback` is 1 when
|
||||
/// a `WAI1` envelope decoded through its manifest's `fallback` capability and 0 when
|
||||
/// through its `capability`; for a `WAI2` envelope `index` names the rendition, and with
|
||||
/// it the capability, and `fallback` is 0. Free `data` with `wai_buffer_free`.
|
||||
/// `wai_neural_decode_ex` writes the whole struct on every call that passes a
|
||||
/// non-null `out`: on failure `kind` is 0 and `data` is empty, so `data` is always
|
||||
/// safe to free.
|
||||
/// non-null `out`: on failure `kind`, `path` and `fallback` are 0 and `data` is empty,
|
||||
/// so `data` is always safe to free.
|
||||
#[repr(C)]
|
||||
pub struct WaiDecoded {
|
||||
pub kind: u32,
|
||||
|
|
@ -212,6 +228,8 @@ pub struct WaiDecoded {
|
|||
pub sample_rate: u32,
|
||||
pub n_frames: u32,
|
||||
pub fps_x_1000: u32,
|
||||
pub path: u32,
|
||||
pub fallback: u32,
|
||||
}
|
||||
|
||||
impl WaiDecoded {
|
||||
|
|
@ -219,7 +237,7 @@ impl WaiDecoded {
|
|||
fn empty() -> Self {
|
||||
WaiDecoded {
|
||||
kind: 0, index: 0, data: WaiBuffer::empty(),
|
||||
width: 0, height: 0, sample_rate: 0, n_frames: 0, fps_x_1000: 0,
|
||||
width: 0, height: 0, sample_rate: 0, n_frames: 0, fps_x_1000: 0, path: 0, fallback: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -285,19 +303,26 @@ pub unsafe extern "C" fn wai_neural_decode_ex(
|
|||
let (code, msg) = decode_status(e);
|
||||
if left.is_empty() { (code, msg) } else { (code, format!("{msg}; {}", no_kind(&left).1)) }
|
||||
};
|
||||
let (index, decoded) = if version == 1 {
|
||||
let (index, fallback, d) = if version == 1 {
|
||||
let env = Wai::from_bytes(bytes).map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?;
|
||||
let d = if strict { decode_envelope_strict(&env, ®) } else { decode_envelope(&env, ®) };
|
||||
let named = [Some(env.capability()), env.fallback()].into_iter().flatten().collect();
|
||||
(0, d.map_err(|e| explain(named, e))?)
|
||||
let d = d.map_err(|e| explain(named, e))?;
|
||||
(0, d.capability != env.capability(), d)
|
||||
} else {
|
||||
let m = WaiMulti::from_bytes(bytes).map_err(|e| (WAI_ERR_CONTAINER, format!("envelope parse: {e}")))?;
|
||||
let r = if strict { decode_multi_strict(&m, ®) } else { decode_multi(&m, ®) };
|
||||
let named = m.manifest.renditions.iter().map(|r| r.capability.as_str()).collect();
|
||||
r.map_err(|e| explain(named, e))?
|
||||
let (i, d) = r.map_err(|e| explain(named, e))?;
|
||||
(i, false, d)
|
||||
};
|
||||
let mut o = WaiDecoded { index: index as u32, ..WaiDecoded::empty() };
|
||||
match decoded {
|
||||
let path = match d.path {
|
||||
DecodePath::Exact => WAI_DECODE_PATH_EXACT,
|
||||
DecodePath::Float => WAI_DECODE_PATH_FLOAT,
|
||||
DecodePath::Sole => WAI_DECODE_PATH_SOLE,
|
||||
};
|
||||
let mut o = WaiDecoded { index: index as u32, path, fallback: u32::from(fallback), ..WaiDecoded::empty() };
|
||||
match d.output {
|
||||
Decoded::Audio(a) => {
|
||||
o.kind = WAI_DECODED_AUDIO;
|
||||
o.sample_rate = a.sample_rate;
|
||||
|
|
|
|||
|
|
@ -184,6 +184,23 @@ impl<'a> IntParams<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The parameter set `cap`'s decode reads from these parameters, named as a pin over
|
||||
/// exactly those bytes names it (SPEC §3.1): for a capability registered with a one-file
|
||||
/// set, the SHA-256 of that file, which a form-less pin names; for another, the SHA-256
|
||||
/// of the listing of the members read, which a pin in the form `wai.prior.set` over them
|
||||
/// names. `None` where these parameters hold no set, or none of the capability's
|
||||
/// members. A record of an unpinned decode names its set by it (jwp-receipts §2.4.12).
|
||||
pub fn read_pin(&self, cap: &str) -> Option<String> {
|
||||
let read = self.read(cap);
|
||||
if self.is_none() || read.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if crate::container::pin_shape(cap) == crate::codecs::ParameterSetShape::OneFile {
|
||||
return set_members(cap).first().and_then(|m| read.get(*m)).map(|b| crate::pin::sha256_hex(b));
|
||||
}
|
||||
self.read_digest(cap).map(|d| d.iter().map(|b| format!("{b:02x}")).collect())
|
||||
}
|
||||
|
||||
/// Whether a pin verified these parameters.
|
||||
pub fn is_pinned(&self) -> bool {
|
||||
matches!(self.0, Params::Verified(_) | Params::Held { pinned: true, .. })
|
||||
|
|
|
|||
|
|
@ -618,6 +618,30 @@ fn run(case: &Value, objects: &Objects) -> (Vec<Value>, Option<Value>) {
|
|||
(answers, outcome)
|
||||
}
|
||||
|
||||
/// A case as the conformance runner gives it to a sink, its answers withheld (`wai conform sink
|
||||
/// --suite native-sink`): `objects`, the bodies its calls name, hexadecimal; `config`, `seed`
|
||||
/// and `start_us`; and `calls`, each call's `do`. A configuration the corpus states a session
|
||||
/// refuses is a case of no call. What this module answers: a session opened from `config` under
|
||||
/// the key whose seed is `seed`, and each call's answer in order; or, where the configuration is
|
||||
/// refused, that refusal alone, `{"error": <reason>}`.
|
||||
pub fn answer_case(case: &Value) -> Result<Vec<Value>, String> {
|
||||
let objects: Objects = case["objects"]
|
||||
.as_object()
|
||||
.ok_or("no objects")?
|
||||
.iter()
|
||||
.map(|(k, v)| v.as_str().and_then(audit::unhex).map(|b| (k.clone(), b)).ok_or_else(|| format!("object {k} is not hexadecimal")))
|
||||
.collect::<Result<_, String>>()?;
|
||||
let cfg = match Config::from_json(&case["config"]) {
|
||||
Ok(c) => c,
|
||||
Err(r) => return Ok(vec![json!({"error": r.code()})]),
|
||||
};
|
||||
let seed = case["seed"].as_str().and_then(unhex32).ok_or("seed: 32 bytes, hexadecimal")?;
|
||||
let start = case["start_us"].as_u64().ok_or("start_us: an integer")?;
|
||||
let mut sink = Sink::open(cfg, SigningKey::from_bytes(&seed), start);
|
||||
let calls = case["calls"].as_array().ok_or("no calls")?;
|
||||
Ok(calls.iter().map(|c| run_call(&mut sink, &objects, &c["do"])).collect())
|
||||
}
|
||||
|
||||
/// The corpus document.
|
||||
pub fn generate() -> Value {
|
||||
let mut objects: Objects = BTreeMap::new();
|
||||
|
|
|
|||
|
|
@ -523,6 +523,54 @@ pub enum Decoded {
|
|||
Int(crate::int_payload::IntDecoded),
|
||||
}
|
||||
|
||||
/// Which of a capability's decode paths gave a decode's output
|
||||
/// (extensions/determinism-tiers.md §5). A sink states it in a `reconstruct-session`
|
||||
/// claim as a decoder entry's `variant` ([`DecodePath::variant`],
|
||||
/// extensions/jwp-receipts.md §2.4.12): the path that ran, which a receipt reports
|
||||
/// beside the tier the capability is registered at.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum DecodePath {
|
||||
/// The capability's one decode path: every capability this dispatch decodes but
|
||||
/// `wai.image.jpegai`.
|
||||
Sole,
|
||||
/// `wai.image.jpegai`'s exact decoder (`jpegai_decode`): the reconstruction a
|
||||
/// decoded-pixel hash or a reconstruction binding over the capability is over.
|
||||
Exact,
|
||||
/// `wai.image.jpegai`'s float decode through ONNX Runtime, taken by a sink without
|
||||
/// the integer weight bundle, or on a thread or target that cannot run the exact
|
||||
/// decoder's float stages. Its symbols are the exact decoder's; its pixels are not
|
||||
/// pinned, so no decoded-pixel hash or binding is checked over them.
|
||||
Float,
|
||||
}
|
||||
|
||||
impl DecodePath {
|
||||
/// The `variant` a `reconstruct-session` decoder entry states for this path: `None`
|
||||
/// for a capability with one path, `exact` for the exact decoder, `float` for the
|
||||
/// float decode (extensions/jwp-receipts.md §2.4.12).
|
||||
pub fn variant(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Self::Sole => None,
|
||||
Self::Exact => Some("exact"),
|
||||
Self::Float => Some("float"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A decode's output, with what produced it: the capability the payload decoded through
|
||||
/// and the path of that capability that ran (extensions/determinism-tiers.md §5).
|
||||
#[derive(Debug, Clone)]
|
||||
#[non_exhaustive]
|
||||
pub struct Dispatched {
|
||||
/// The capability the payload decoded through: a `WAI1` manifest's `capability` or
|
||||
/// its `fallback` (SPEC §4 steps 3 and 5), or the picked `WAI2` rendition's.
|
||||
pub capability: String,
|
||||
/// Which of that capability's decode paths ran.
|
||||
pub path: DecodePath,
|
||||
/// The decoded output.
|
||||
pub output: Decoded,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum DecodeError {
|
||||
|
|
@ -605,9 +653,10 @@ fn is_unsupported(e: &DecodeError, cap: &str) -> bool {
|
|||
}
|
||||
|
||||
/// Decode `payload` through `capability` against `params` — the per-capability
|
||||
/// dispatch, shared by the `WAI1` and `WAI2` paths.
|
||||
pub fn decode_payload(capability: &str, payload: &[u8], params: &ParamSource) -> Result<Decoded, DecodeError> {
|
||||
decode_payload_within(capability, payload, params, None)
|
||||
/// dispatch, shared by the `WAI1` and `WAI2` paths. The result names the capability and
|
||||
/// the path of it that ran ([`Dispatched`]).
|
||||
pub fn decode_payload(capability: &str, payload: &[u8], params: &ParamSource) -> Result<Dispatched, DecodeError> {
|
||||
decode_payload_within(capability, payload, params, None).map(|(output, path)| Dispatched { capability: capability.to_string(), path, output })
|
||||
}
|
||||
|
||||
/// What decoding `payload` through the integer capability `capability` against
|
||||
|
|
@ -624,11 +673,14 @@ pub fn declared_cost(capability: &str, payload: &[u8], params: &ParamSource) ->
|
|||
}
|
||||
|
||||
/// [`decode_payload`] with a cost limit applied to the integer capabilities first
|
||||
/// ([`ModelRegistry::with_cost_limit`]).
|
||||
/// ([`ModelRegistry::with_cost_limit`]): the output, and the path that gave it.
|
||||
fn decode_payload_within(
|
||||
capability: &str, payload: &[u8], params: &ParamSource, limit: Option<&crate::int_payload::DeclaredCost>,
|
||||
) -> Result<Decoded, DecodeError> {
|
||||
match capability {
|
||||
) -> Result<(Decoded, DecodePath), DecodeError> {
|
||||
if capability == crate::codecs::CAP_IMAGE_JPEGAI {
|
||||
return decode_jpegai(payload, params);
|
||||
}
|
||||
let sole = match capability {
|
||||
"wai.neural.encodec32" => audio::decode(payload, params, 32_000).map(Decoded::Audio),
|
||||
"wai.neural.dac" => audio::decode(payload, params, 44_100).map(Decoded::Audio),
|
||||
"wai.neural.mimi" => audio::decode(payload, params, 24_000).map(Decoded::Audio),
|
||||
|
|
@ -662,41 +714,45 @@ fn decode_payload_within(
|
|||
// orchestration; their temporal nets are sink-supplied.
|
||||
"wai.neural.dcvc_rt" | "wai.neural.dcvc_fm"
|
||||
=> video_dcvc::decode(payload, params).map(Decoded::Video),
|
||||
// ISO/IEC 6048 JPEG AI over a sink-supplied model bundle (several files).
|
||||
"wai.image.jpegai" => {
|
||||
// Both decode paths implement one configuration (tools_0, β = 0, the
|
||||
// high-operation-point synthesis, one eICCI model pair, 256×256 4:4:4);
|
||||
// a codestream that asks for anything else is refused, not mis-decoded.
|
||||
crate::jpegai_decode::check_codestream(payload).map_err(|why| {
|
||||
DecodeError::InvalidPayload(format!("wai.image.jpegai: unsupported codestream: {why}"))
|
||||
})?;
|
||||
// Prefer the exact decoder when the parameters include the integer weight
|
||||
// bundle (`norm.json` + `weights/*.json`) — that path pins one
|
||||
// reconstruction, reproducible across machines under the conditions in
|
||||
// `extensions/determinism-tiers.md` §5. Without the bundle, or in a
|
||||
// non-default floating-point environment, the ORT float decode runs; it
|
||||
// needs files a set holding only the exact bundle does not contain, so a
|
||||
// pinned decode is then refused (`set_incomplete`) rather than run on
|
||||
// unverified files. A refusal of the exact decode itself (`model_mismatch`
|
||||
// for its bundle, `stream_exhausted` for a substream, integer-operators
|
||||
// §13.6.1) is the decode's result: it does not fall back to the float
|
||||
// decode. Both paths decode the scale path and the entropy through the
|
||||
// integer operator set (`jpegai_ops`), which is all the capability
|
||||
// guarantees (entropy-consistency, `NeuralEntropyExact`).
|
||||
match jpegai_exact(payload, params) {
|
||||
Exact::Decoded(img) => Ok(Decoded::Image(img)),
|
||||
Exact::Refused(e) => Err(e),
|
||||
Exact::Unavailable => {
|
||||
let to_err = |e: jpegai::JpegAiError| match e {
|
||||
jpegai::JpegAiError::Pin(r) => DecodeError::PriorMismatch(r),
|
||||
other => DecodeError::InvalidPayload(other.to_string()),
|
||||
};
|
||||
let model = jpegai::JpegAiModel::load_from(params.clone()).map_err(to_err)?;
|
||||
jpegai::decode(payload, &model).map(Decoded::Image).map_err(to_err)
|
||||
}
|
||||
}
|
||||
}
|
||||
other => Err(DecodeError::UnknownCapability(other.to_string())),
|
||||
};
|
||||
sole.map(|d| (d, DecodePath::Sole))
|
||||
}
|
||||
|
||||
/// ISO/IEC 6048 JPEG AI over a sink-supplied model bundle (several files), through the
|
||||
/// exact decoder or the float decode, with the path that ran.
|
||||
fn decode_jpegai(payload: &[u8], params: &ParamSource) -> Result<(Decoded, DecodePath), DecodeError> {
|
||||
// Both decode paths implement one configuration (tools_0, β = 0, the
|
||||
// high-operation-point synthesis, one eICCI model pair, 256×256 4:4:4);
|
||||
// a codestream that asks for anything else is refused, not mis-decoded.
|
||||
crate::jpegai_decode::check_codestream(payload).map_err(|why| {
|
||||
DecodeError::InvalidPayload(format!("wai.image.jpegai: unsupported codestream: {why}"))
|
||||
})?;
|
||||
// Prefer the exact decoder when the parameters include the integer weight
|
||||
// bundle (`norm.json` + `weights/*.json`) — that path pins one
|
||||
// reconstruction, reproducible across machines under the conditions in
|
||||
// `extensions/determinism-tiers.md` §5. Without the bundle, or in a
|
||||
// non-default floating-point environment, the ORT float decode runs; it
|
||||
// needs files a set holding only the exact bundle does not contain, so a
|
||||
// pinned decode is then refused (`set_incomplete`) rather than run on
|
||||
// unverified files. A refusal of the exact decode itself (`model_mismatch`
|
||||
// for its bundle, `stream_exhausted` for a substream, integer-operators
|
||||
// §13.6.1) is the decode's result: it does not fall back to the float
|
||||
// decode. Both paths decode the scale path and the entropy through the
|
||||
// integer operator set (`jpegai_ops`), which is all the capability
|
||||
// guarantees (entropy-consistency, `NeuralEntropyExact`). The path that ran is
|
||||
// reported with the image, so a receipt or a binding check can tell them apart.
|
||||
match jpegai_exact(payload, params) {
|
||||
Exact::Decoded(img) => Ok((Decoded::Image(img), DecodePath::Exact)),
|
||||
Exact::Refused(e) => Err(e),
|
||||
Exact::Unavailable => {
|
||||
let to_err = |e: jpegai::JpegAiError| match e {
|
||||
jpegai::JpegAiError::Pin(r) => DecodeError::PriorMismatch(r),
|
||||
other => DecodeError::InvalidPayload(other.to_string()),
|
||||
};
|
||||
let model = jpegai::JpegAiModel::load_from(params.clone()).map_err(to_err)?;
|
||||
jpegai::decode(payload, &model).map(|img| (Decoded::Image(img), DecodePath::Float)).map_err(to_err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -774,7 +830,7 @@ fn decode_envelope_with(
|
|||
env: &Wai,
|
||||
registry: &ModelRegistry,
|
||||
gate: impl Fn(&str) -> Result<(), DecodeError>,
|
||||
) -> Result<Decoded, DecodeError> {
|
||||
) -> Result<Dispatched, DecodeError> {
|
||||
let mut excluded: Vec<String> = Vec::new();
|
||||
// The first refusal raised after dispatch picked a capability — `set_incomplete`
|
||||
// from a pinned decode, or the key log's refusal of an unpinned one — with that
|
||||
|
|
@ -822,7 +878,8 @@ fn decode_envelope_with(
|
|||
incomplete = Some((d.capability.to_string(), e));
|
||||
}
|
||||
}
|
||||
r => return r,
|
||||
Err(e) => return Err(e),
|
||||
Ok((output, path)) => return Ok(Dispatched { capability: d.capability.to_string(), path, output }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -833,8 +890,10 @@ fn decode_envelope_with(
|
|||
/// verified. An envelope whose manifest declares an encryption is inert here
|
||||
/// ([`DecodeError::Encrypted`], SPEC §6.4 step 2): this sink opens no scheme. Returns [`DecodeError::PriorMismatch`] with the first refusal when no
|
||||
/// capability's pin resolves, and [`DecodeError::ModelNotRegistered`] when the
|
||||
/// registry holds neither capability.
|
||||
pub fn decode_envelope(env: &Wai, registry: &ModelRegistry) -> Result<Decoded, DecodeError> {
|
||||
/// registry holds neither capability. The output comes with the capability it decoded
|
||||
/// through and the path of it that ran ([`Dispatched`]), which a receipt of the decode
|
||||
/// states (extensions/jwp-receipts.md §2.4.12).
|
||||
pub fn decode_envelope(env: &Wai, registry: &ModelRegistry) -> Result<Dispatched, DecodeError> {
|
||||
decode_envelope_with(env, registry, |_| Ok(()))
|
||||
}
|
||||
|
||||
|
|
@ -883,7 +942,7 @@ fn replicate_gate(cap: &str, refusal: Option<crate::codecs::ReplicateRefusal>) -
|
|||
/// inference through a runtime is not byte-identical across hardware, and even a
|
||||
/// fixed-order float stage depends on the build and on the process's floating-point
|
||||
/// environment, which neither the envelope nor the registry can state.
|
||||
pub fn decode_envelope_strict(env: &Wai, registry: &ModelRegistry) -> Result<Decoded, DecodeError> {
|
||||
pub fn decode_envelope_strict(env: &Wai, registry: &ModelRegistry) -> Result<Dispatched, DecodeError> {
|
||||
let cap = env.capability();
|
||||
let mut candidates: Vec<&str> = [Some(cap), env.fallback()].into_iter().flatten()
|
||||
.filter(|c| crate::codecs::is_dispatchable(c) && registry.advertises(c))
|
||||
|
|
@ -904,10 +963,11 @@ pub fn decode_envelope_strict(env: &Wai, registry: &ModelRegistry) -> Result<Dec
|
|||
/// pinned decode refused with `set_incomplete`, an unpinned entry whose set the
|
||||
/// registry's key log refuses, or an integer payload over the registry's cost limit
|
||||
/// (`cost_over_limit`), excludes that entry and picks again.
|
||||
/// Returns the picked index with the output, or [`DecodeError::NoRendition`] with a
|
||||
/// Returns the picked index with the output, the rendition's capability and the path
|
||||
/// of it that ran ([`Dispatched`]), or [`DecodeError::NoRendition`] with a
|
||||
/// reason per entry; an envelope whose manifest declares an encryption gives every
|
||||
/// entry `unsupported_encryption` (SPEC §6.4 step 2).
|
||||
pub fn decode_multi(m: &WaiMulti, registry: &ModelRegistry) -> Result<(usize, Decoded), DecodeError> {
|
||||
pub fn decode_multi(m: &WaiMulti, registry: &ModelRegistry) -> Result<(usize, Dispatched), DecodeError> {
|
||||
decode_multi_where(m, registry, |_| Ok(()))
|
||||
}
|
||||
|
||||
|
|
@ -915,7 +975,7 @@ fn decode_multi_where(
|
|||
m: &WaiMulti,
|
||||
registry: &ModelRegistry,
|
||||
admit: impl Fn(usize) -> Result<(), Skip>,
|
||||
) -> Result<(usize, Decoded), DecodeError> {
|
||||
) -> Result<(usize, Dispatched), DecodeError> {
|
||||
let mut incomplete: Vec<(usize, String)> = Vec::new();
|
||||
loop {
|
||||
let picked = m.pick_where(
|
||||
|
|
@ -953,7 +1013,7 @@ fn decode_multi_where(
|
|||
incomplete.push((p.index, format!("unsupported_capability: {e}")));
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
Ok(d) => return Ok((p.index, d)),
|
||||
Ok((output, path)) => return Ok((p.index, Dispatched { capability: p.meta.capability.clone(), path, output })),
|
||||
}
|
||||
}
|
||||
Err(skips) => {
|
||||
|
|
@ -974,7 +1034,7 @@ fn decode_multi_where(
|
|||
/// one it leaves out is reported with its `replicate_*` reason.
|
||||
/// When none is admitted the result is [`DecodeError::UnsoundReplicate`] with the
|
||||
/// first selectable entry's reason.
|
||||
pub fn decode_multi_strict(m: &WaiMulti, registry: &ModelRegistry) -> Result<(usize, Decoded), DecodeError> {
|
||||
pub fn decode_multi_strict(m: &WaiMulti, registry: &ModelRegistry) -> Result<(usize, Dispatched), DecodeError> {
|
||||
if m.intent() == "replicate" {
|
||||
let selectable: Vec<usize> = m.manifest.renditions.iter().enumerate()
|
||||
.filter(|(_, r)| r.is_selectable()).map(|(i, _)| i).collect();
|
||||
|
|
@ -992,6 +1052,72 @@ pub fn decode_multi_strict(m: &WaiMulti, registry: &ModelRegistry) -> Result<(us
|
|||
decode_multi(m, registry)
|
||||
}
|
||||
|
||||
/// The `decoderId` of the reconstruction `wai.image.jpegai`'s exact decoder computes: its
|
||||
/// high-operation-point synthesis (extensions/reconstruction-binding.md §1).
|
||||
#[cfg(feature = "provenance")]
|
||||
const JPEGAI_EXACT_DECODER_ID: u8 = 2;
|
||||
|
||||
/// What checking a reconstruction binding against a decode found
|
||||
/// ([`check_reconstruction_binding`], extensions/reconstruction-binding.md §2).
|
||||
#[cfg(feature = "provenance")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[non_exhaustive]
|
||||
pub enum BindingCheck {
|
||||
/// The decode computed the reconstruction the binding is over, and its canonical
|
||||
/// buffer hashes to the binding's hash.
|
||||
Holds,
|
||||
/// The decode computed that reconstruction, and its buffer hashes to another value.
|
||||
Differs,
|
||||
/// The decode ran a path that does not compute the reconstruction the binding is
|
||||
/// over: `wai.image.jpegai`'s float decode, or a capability at neither
|
||||
/// `decode-equivalence` nor that capability's exact decoder. The binding was not
|
||||
/// checked, and a mismatch would not be evidence that the asset changed.
|
||||
NotChecked(DecodePath),
|
||||
/// The path that ran computes a reconstruction, but not the one the binding's
|
||||
/// descriptor names: another `decoderId` (`wai.image.jpegai`'s exact decoder computes
|
||||
/// `decoderId` 2), a `format` other than `RGB8`, or a `width` and `height` other than
|
||||
/// the decoded picture's. The binding was not checked: the verifier decoded some other
|
||||
/// way than the descriptor says (extensions/reconstruction-binding.md §2).
|
||||
OtherReconstruction(DecodePath),
|
||||
}
|
||||
|
||||
/// Check a reconstruction binding over the decode of a parsed `WAI1` envelope
|
||||
/// (extensions/reconstruction-binding.md §2): decode it as [`decode_envelope`] does, then
|
||||
/// compare its canonical `RGB8` buffer with the binding only where the path that ran
|
||||
/// computes the reconstruction the binding is over: a capability whose registry class
|
||||
/// gives `decode-equivalence`, or `wai.image.jpegai`'s exact decoder, under a descriptor
|
||||
/// that names what that path computed. Over any other path the binding is reported as not
|
||||
/// checked, with that path, never as failed; under a descriptor that names another
|
||||
/// reconstruction (for the exact decoder, a `decoderId` other than 2; for either, a format
|
||||
/// other than `RGB8` or another picture size) it is reported as
|
||||
/// [`BindingCheck::OtherReconstruction`], never as failed. A decode that is not a picture
|
||||
/// is [`DecodeError::InvalidPayload`]: the binding hashes a picture.
|
||||
#[cfg(feature = "provenance")]
|
||||
pub fn check_reconstruction_binding(
|
||||
env: &Wai, registry: &ModelRegistry, binding: &crate::reconstruction_binding::ReconstructionBinding,
|
||||
) -> Result<BindingCheck, DecodeError> {
|
||||
let d = decode_envelope(env, registry)?;
|
||||
let exact = match d.path {
|
||||
DecodePath::Exact => true,
|
||||
DecodePath::Float => false,
|
||||
DecodePath::Sole => crate::codecs::class_tier(&d.capability) == Some(crate::codecs::DeclaredTier::DecodeEquivalence),
|
||||
};
|
||||
let Decoded::Image(img) = &d.output else {
|
||||
return Err(DecodeError::InvalidPayload(format!("{}: a reconstruction binding is over a picture", d.capability)));
|
||||
};
|
||||
if !exact {
|
||||
return Ok(BindingCheck::NotChecked(d.path));
|
||||
}
|
||||
// The descriptor names the reconstruction the hash is over; a verifier compares only
|
||||
// under it. A single-branch capability's descriptor fixes no branch.
|
||||
let r = &binding.descriptor;
|
||||
let branch = d.path != DecodePath::Exact || r.decoder_id == JPEGAI_EXACT_DECODER_ID;
|
||||
if !branch || r.format != "RGB8" || (r.width, r.height) != (img.width, img.height) {
|
||||
return Ok(BindingCheck::OtherReconstruction(d.path));
|
||||
}
|
||||
Ok(if binding.verify(&img.rgb) { BindingCheck::Holds } else { BindingCheck::Differs })
|
||||
}
|
||||
|
||||
/// What the exact JPEG AI decoder made of a decode ([`jpegai_exact`]).
|
||||
enum Exact {
|
||||
/// The parameters hold no integer weight bundle (no `norm.json`), or this target or
|
||||
|
|
@ -1161,8 +1287,10 @@ mod jpegai_exact_dispatch_tests {
|
|||
let e = e.unwrap();
|
||||
files.insert(format!("weights/{}", e.file_name().to_str().unwrap()), std::fs::read(e.path()).unwrap());
|
||||
}
|
||||
let decode = |files: &std::collections::BTreeMap<String, Vec<u8>>| super::decode_payload("wai.image.jpegai", &payload, &super::ParamSource::UnpinnedRead(files.clone()));
|
||||
let dispatched = |files: &std::collections::BTreeMap<String, Vec<u8>>| super::decode_payload("wai.image.jpegai", &payload, &super::ParamSource::UnpinnedRead(files.clone()));
|
||||
let decode = |files: &std::collections::BTreeMap<String, Vec<u8>>| dispatched(files).map(|d| d.output);
|
||||
assert!(matches!(decode(&files), Ok(super::Decoded::Image(_))), "the intact bundle decodes");
|
||||
assert_eq!(dispatched(&files).unwrap().path, super::DecodePath::Exact, "through the exact decoder, and says so");
|
||||
let mut bad = files.clone();
|
||||
let mut nv: serde_json::Value = serde_json::from_slice(&files["norm.json"]).unwrap();
|
||||
nv["bounds"][0] = serde_json::Value::from(256);
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ fn invalid(s: impl Into<String>) -> MeasureError {
|
|||
MeasureError::Invalid(s.into())
|
||||
}
|
||||
|
||||
/// A base decoded: its identity as the sink holds it, its units, and the SHA-256 of
|
||||
/// the pin its decode verified (`None` when unpinned).
|
||||
/// A base decoded: its identity as the sink holds it, its units, and the parameter set its
|
||||
/// decode read, named as a pin names it: the SHA-256 of the pin it verified, or, unpinned,
|
||||
/// the digest a pin over the bytes it read would name (`staged::BaseDecode::set`); `None`
|
||||
/// for a capability that reads no parameter set.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DecodedBase {
|
||||
pub info: BaseInfo,
|
||||
|
|
@ -67,7 +69,7 @@ fn checked(b: &TitleBase, d: crate::staged::BaseDecode) -> Result<DecodedBase, M
|
|||
return Err(invalid(format!("{at}: the decode gives (via, fallback, tier, pin) {got:?}; the case states {want:?}")));
|
||||
}
|
||||
}
|
||||
Ok(DecodedBase { info, units: d.units, pin: d.pin.map(|p| p.sha256) })
|
||||
Ok(DecodedBase { info, units: d.units, pin: d.set })
|
||||
}
|
||||
|
||||
/// The chain configuration a replay runs under: the title's chain budget, its cost
|
||||
|
|
@ -96,7 +98,8 @@ pub struct StageEvent<'a> {
|
|||
pub layers: u16,
|
||||
/// The capability that produced it: the base's, or the layer's.
|
||||
pub capability: &'a str,
|
||||
/// The pin the base's decode verified, for stage 0.
|
||||
/// The parameter set the base's decode read, as a pin names it ([`DecodedBase`]), for
|
||||
/// stage 0.
|
||||
pub pin: Option<&'a str>,
|
||||
/// The content hash of the envelope that produced it: the base's, or the layer's.
|
||||
pub object: [u8; 32],
|
||||
|
|
|
|||
|
|
@ -248,6 +248,46 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
/// SPEC §3.1, *Pin by default*: a base its encoder left unpinned is still decoded against
|
||||
/// the set the sink holds, and the session's decoder entry names that set by the digest a
|
||||
/// pin over the bytes read names (jwp-receipts §2.4.12), with the tier `none`. The
|
||||
/// fixture's own pin, a listing of the same files, is that digest; the pinned base
|
||||
/// names it as before, with the tier its pin declares.
|
||||
#[test]
|
||||
fn an_unpinned_decode_names_the_set_it_read() {
|
||||
let wai = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("..");
|
||||
let key = SigningKey::from_bytes(&[0x44; 32]);
|
||||
let host = SessionHost { profile: "native-ffi".into(), target: None, build: BuildRecord::of_this_build(), threads: 1 };
|
||||
let hex = |b: &[u8; 32]| b.iter().map(|x| format!("{x:02x}")).collect::<String>();
|
||||
let entry_of = |t: &Title| {
|
||||
let e = super::super::run::evaluate(t).unwrap();
|
||||
let (r, entry) = recorder_of(t, &e, host.clone(), [3; 32]).unwrap();
|
||||
let s = r.finish(entry, &key, "did:example:sink", 0, None, "2026-10-06T12:00:00Z").unwrap();
|
||||
s.claim.verify().unwrap();
|
||||
let ClaimBody::ReconstructSession(b) = s.claim.body else { panic!("a session") };
|
||||
let d = b.decoders[0].clone();
|
||||
(d.pin.as_ref().map(hex), d.tier)
|
||||
};
|
||||
let mut t = Title::load(&wai, "staged-measure-conformance/fixtures/still").unwrap();
|
||||
// The base alone: its layers name the pinned base by its content hash, and its source
|
||||
// is the size of their final stage.
|
||||
t.layers.clear();
|
||||
t.references.clear();
|
||||
t.source = None;
|
||||
t.expect = None;
|
||||
let pinned = Wai::from_bytes(&t.bases[0].obj.bytes).unwrap();
|
||||
let pin = pinned.prior().expect("the fixture's base is pinned").sha256.clone();
|
||||
assert_eq!(entry_of(&t), (Some(pin.clone()), Some("decode-equivalence".to_string())));
|
||||
|
||||
let mut unpinned = Wai::from_bytes(&t.bases[0].obj.bytes).unwrap();
|
||||
unpinned.manifest.conditioning.prior = None;
|
||||
unpinned.manifest.intent = "improve".into();
|
||||
assert!(unpinned.check_unpinned().is_some(), "the planted base is reported prior_unpinned");
|
||||
t.bases[0].obj.bytes = unpinned.to_bytes().unwrap();
|
||||
t.bases[0].case = None;
|
||||
assert_eq!(entry_of(&t), (Some(pin), Some("none".to_string())), "the set it read, by the digest its pin would have named");
|
||||
}
|
||||
|
||||
/// A metered session, as `wai_stage_measure bench --sign` seals one: the figure a live
|
||||
/// from-rest bracket gives under `OnChipCounter`, in the session's `energy`, and the
|
||||
/// `measurement` claim on it by the same key, chained after the session and checked
|
||||
|
|
|
|||
|
|
@ -492,10 +492,48 @@ fn replay_session(dir: &std::path::Path) -> Option<Replayed> {
|
|||
use crate::session_recorder::{DecoderKey, SessionRecorder};
|
||||
let key = ed25519_dalek::SigningKey::from_bytes(&[0x44; 32]);
|
||||
let case: Value = serde_json::from_slice(&std::fs::read(dir.join("case.json")).expect("case.json")).expect("JSON");
|
||||
// What the sink's decoder entry for a base names as its parameter set (jwp-receipts
|
||||
// §2.4.12): the pin its decode verified, with the tier that pin declares; for an
|
||||
// unpinned decode that read a set, the digest a pin over the bytes it read names, with
|
||||
// the tier `none` (SPEC §3.1, *Pin by default*, as `int_payload::IntParams::read_pin`
|
||||
// names it); nothing for a decode that read no set. A stage hash over a
|
||||
// `NeuralIntegerExact` base needs the entry at `decode-equivalence` (determinism-tiers
|
||||
// §3, rule 3), so a sink that names no pin seals no session over its stage hash.
|
||||
let wai_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..");
|
||||
let named_set = |b: &Value, env: &[u8]| -> (Option<[u8; 32]>, Option<String>) {
|
||||
use crate::codecs::DeclaredTier;
|
||||
if b["pin_resolved"] == true {
|
||||
let w = Wai::from_bytes(env).expect("a base envelope");
|
||||
let p = w.prior().expect("a pin that resolved is the envelope's");
|
||||
let mut pin = [0u8; 32];
|
||||
for (i, x) in pin.iter_mut().enumerate() {
|
||||
*x = u8::from_str_radix(&p.sha256[2 * i..2 * i + 2], 16).expect("a SHA-256 in hexadecimal");
|
||||
}
|
||||
let tier = p.tier().and_then(DeclaredTier::from_token).unwrap_or(DeclaredTier::None);
|
||||
return (Some(pin), Some(tier.token().to_owned()));
|
||||
}
|
||||
let read: BTreeMap<String, Vec<u8>> = b["decode"]["set"]
|
||||
.as_object()
|
||||
.map(|s| s.iter().map(|(m, p)| (m.clone(), std::fs::read(wai_dir.join(p.as_str().expect("a path"))).expect("a set file"))).collect())
|
||||
.unwrap_or_default();
|
||||
if read.is_empty() {
|
||||
return (None, None);
|
||||
}
|
||||
let cap = b["decoded_via"].as_str().expect("decoded_via");
|
||||
let digest = if crate::container::pin_shape(cap) == crate::codecs::ParameterSetShape::OneFile {
|
||||
crate::pin::sha256(read.values().next().expect("one file"))
|
||||
} else {
|
||||
let files: BTreeMap<String, &[u8]> = read.iter().map(|(m, x)| (m.clone(), x.as_slice())).collect();
|
||||
crate::pin::sha256(&crate::pin::listing_from(&files).expect("a listing of the set"))
|
||||
};
|
||||
(Some(digest), Some(DeclaredTier::None.token().to_owned()))
|
||||
};
|
||||
let mut bases = Vec::new();
|
||||
let mut base_sets = Vec::new();
|
||||
for b in case["bases"].as_array().expect("bases") {
|
||||
let env = std::fs::read(dir.join(b["file"].as_str().expect("file"))).expect("base");
|
||||
let stage0 = std::fs::read(dir.join(b["stage0"].as_str().expect("stage0"))).expect("stage0");
|
||||
base_sets.push(named_set(b, &env));
|
||||
let info = BaseInfo {
|
||||
content_hash: chash(&env),
|
||||
capability: b["decoded_via"].as_str().expect("decoded_via").into(),
|
||||
|
|
@ -619,14 +657,15 @@ fn replay_session(dir: &std::path::Path) -> Option<Replayed> {
|
|||
let mut rec = SessionRecorder::new([7; 32], "video", host, None);
|
||||
let mut handles = BTreeMap::new();
|
||||
for (&(unit, layers), source) in &presented {
|
||||
// The capability, and the title's unit that is unit 0 of the object's base.
|
||||
let (capability, first_unit) = match bases.iter().find(|(info, _, _)| info.content_hash == *source) {
|
||||
Some((info, _, _)) => (info.capability.clone(), info.first_unit),
|
||||
// The capability, the title's unit that is unit 0 of the object's base, and, for a
|
||||
// base, the parameter set its entry names.
|
||||
let (capability, first_unit, (pin, tier)) = match bases.iter().position(|(info, _, _)| info.content_hash == *source) {
|
||||
Some(i) => (bases[i].0.capability.clone(), bases[i].0.first_unit, base_sets[i].clone()),
|
||||
None => {
|
||||
let w = files.values().find(|f| chash(f) == *source).and_then(|f| Wai::from_bytes(f).ok()).expect("a layer");
|
||||
let base = w.refines().and_then(Result::ok).map(|r| r.base).expect("a layer refines a base");
|
||||
let first = bases.iter().find(|(info, _, _)| hex(&info.content_hash) == base).map_or(0, |(info, _, _)| info.first_unit);
|
||||
(w.capability().to_owned(), first)
|
||||
(w.capability().to_owned(), first, (None, None))
|
||||
}
|
||||
};
|
||||
let h = *handles.entry((capability.clone(), *source)).or_insert_with(|| {
|
||||
|
|
@ -638,8 +677,8 @@ fn replay_session(dir: &std::path::Path) -> Option<Replayed> {
|
|||
path: SessionPath::Primary,
|
||||
decoder: "builtin:[email protected]".into(),
|
||||
variant: None,
|
||||
pin: None,
|
||||
tier: None,
|
||||
pin,
|
||||
tier,
|
||||
module_status: None,
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -296,11 +296,17 @@ pub struct BaseDecode {
|
|||
/// the decode read only bytes it verified; `None` for an unpinned decode, or a
|
||||
/// capability that reads no parameter set.
|
||||
pub pin: Option<crate::container::Prior>,
|
||||
/// The parameter set the decode read, named as a pin names it (SPEC §3.1, *Pin by
|
||||
/// default*): the `sha256` of the pin it verified; for an unpinned decode, the digest
|
||||
/// a pin over the bytes it read would name (`int_payload::IntParams::read_pin`). `None`
|
||||
/// for a capability that reads no parameter set. A record of the decode names its set
|
||||
/// by it (jwp-receipts §2.4.12).
|
||||
pub set: Option<String>,
|
||||
}
|
||||
|
||||
/// [`decode_base_via`], also giving the pin the decode verified: what a record of the
|
||||
/// decode names as its parameter set (`extensions/staged-measurement.md` Appendix C,
|
||||
/// the decoder entries).
|
||||
/// [`decode_base_via`], also giving the pin the decode verified and the set it read: what
|
||||
/// a record of the decode names as its parameter set (`extensions/staged-measurement.md`
|
||||
/// Appendix C, the decoder entries).
|
||||
pub fn decode_base_pinned(
|
||||
envelope: &[u8],
|
||||
via: Option<&str>,
|
||||
|
|
@ -379,6 +385,10 @@ pub fn decode_base_pinned(
|
|||
};
|
||||
let units = units_from_decoded(decoded).filter(|u| !u.is_empty()).ok_or(BaseError::NoUnits)?;
|
||||
let pin = if params.is_pinned() { w.prior().cloned() } else { None };
|
||||
let read = match &pin {
|
||||
Some(p) => Some(p.sha256.clone()),
|
||||
None => params.read_pin(&cap),
|
||||
};
|
||||
let info = BaseInfo {
|
||||
content_hash: crate::jwp_hash::content_hash(envelope),
|
||||
tier: tier::base_tier(&cap, pin.as_ref()),
|
||||
|
|
@ -387,7 +397,7 @@ pub fn decode_base_pinned(
|
|||
attested: false,
|
||||
first_unit: 0,
|
||||
};
|
||||
return Ok(BaseDecode { info, units, pin });
|
||||
return Ok(BaseDecode { info, units, pin, set: read });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
163
wai/wai-rs/tests/cli_conform_sinks.rs
Normal file
163
wai/wai-rs/tests/cli_conform_sinks.rs
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
//! `conform-result/1` run against WAI's native sink (conformance-results §4.1), through its Rust
|
||||
//! module and through the C interface its platform integrations link, and against the score
|
||||
//! decoder, each run through `wai conform` as another implementation would be. Each run
|
||||
//! is conformant, `wai verify` accepts each record, and `wai verify --conformant` admits each
|
||||
//! against the corpus of this checkout. A planted sink that presents an object whose digest does
|
||||
//! not match is refused (`not_conformant`); the same sink run against a copy of the corpus changed
|
||||
//! to suit it is conformant there, and refused against this checkout's (`corpus_mismatch`); a
|
||||
//! record of a suite this build does not run is refused (`suite_unknown`).
|
||||
#![cfg(all(feature = "quickstart", feature = "score", feature = "native_sink"))]
|
||||
|
||||
use serde_json::Value;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, Output};
|
||||
|
||||
fn tmp(name: &str) -> PathBuf {
|
||||
let d = std::env::temp_dir().join(format!("wai-conform-sinks-{name}-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
std::fs::create_dir_all(&d).unwrap();
|
||||
d
|
||||
}
|
||||
|
||||
fn wai_dir() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
|
||||
}
|
||||
|
||||
fn wai(dir: &Path, args: &[&str]) -> Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_wai")).current_dir(dir).args(args).output().expect("wai runs")
|
||||
}
|
||||
|
||||
fn text(o: &Output) -> String {
|
||||
format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr))
|
||||
}
|
||||
|
||||
fn q(p: &str) -> String {
|
||||
format!("'{}'", p.replace('\'', "'\\''"))
|
||||
}
|
||||
|
||||
/// `wai conform <kind> --suite <suite> --impl <imp> --root <root> --json`, its record written
|
||||
/// to `out`: the exit status and the JSON envelope.
|
||||
fn conform(dir: &Path, root: &Path, kind: &str, suite: &str, imp: &str, out: &Path) -> (i32, Value) {
|
||||
let o = wai(dir, &["conform", kind, "--suite", suite, "--impl", imp, "--root", root.to_str().unwrap(), "--out", out.to_str().unwrap(), "--yes", "--json"]);
|
||||
let v: Value = serde_json::from_slice(&o.stdout).unwrap_or_else(|_| panic!("JSON: {}", text(&o)));
|
||||
(o.status.code().unwrap_or(-1), v)
|
||||
}
|
||||
|
||||
/// The reference native sink, through its module or its C interface.
|
||||
fn native(c_abi: bool) -> String {
|
||||
format!("{} answers {}{{case}}", q(env!("CARGO_BIN_EXE_wai_native_sink_vectors")), if c_abi { "--c-abi " } else { "" })
|
||||
}
|
||||
|
||||
/// A sink that presents an object whose digest does not match the SHA-256 its name states: the
|
||||
/// reference sink's answers with that refusal's `present` turned true.
|
||||
fn planted() -> String {
|
||||
format!("{} | sed '/object_digest_mismatch/s/\"present\":false/\"present\":true/'", native(false))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_native_sink_and_the_score_decoder_conform_and_each_record_is_admitted() {
|
||||
let d = tmp("own");
|
||||
let root = wai_dir();
|
||||
let w = q(env!("CARGO_BIN_EXE_wai"));
|
||||
let runs = [
|
||||
("sink", "native-sink", native(false), "native-sink"),
|
||||
("sink", "native-sink", native(true), "native-sink-c-abi"),
|
||||
("decoder", "score", format!("{w} score render {{in}} --pcm {{out}}"), "score"),
|
||||
];
|
||||
for (kind, suite, imp, name) in &runs {
|
||||
let (code, v) = conform(&d, &root, kind, suite, imp, &d.join(format!("{name}.wai-conform.json")));
|
||||
assert_eq!(code, 0, "{name}: {v:#}");
|
||||
assert_eq!(v["data"]["conformant"], true, "{name}");
|
||||
assert_eq!(v["data"]["failed"], 0, "{name}");
|
||||
}
|
||||
let rec: Value = serde_json::from_slice(&std::fs::read(d.join("native-sink.wai-conform.json")).unwrap()).unwrap();
|
||||
assert_eq!((rec["kind"].as_str(), rec["suite"].as_str()), (Some("sink"), Some("native-sink")));
|
||||
// Every session of the corpus and every configuration it refuses is a case.
|
||||
let corpus: Value = serde_json::from_slice(&std::fs::read(root.join("live-conformance/native-sink/vectors.json")).unwrap()).unwrap();
|
||||
let n = corpus["cases"].as_array().unwrap().len() + corpus["configs"].as_array().unwrap().len();
|
||||
assert_eq!(rec["summary"]["cases"].as_u64(), Some(n as u64), "{}", rec["summary"]);
|
||||
|
||||
let o = wai(&d, &["verify", d.to_str().unwrap()]);
|
||||
assert!(o.status.success() && text(&o).contains("3 conformance results verified"), "{}", text(&o));
|
||||
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), d.to_str().unwrap()]);
|
||||
assert!(o.status.success(), "{}", text(&o));
|
||||
assert_eq!(text(&o).matches("ok admitted").count(), 3, "{}", text(&o));
|
||||
assert!(text(&o).contains("every conformance result admits its implementation"), "{}", text(&o));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_planted_failing_sink_is_refused() {
|
||||
let d = tmp("planted");
|
||||
let root = wai_dir();
|
||||
let out = d.join("planted.wai-conform.json");
|
||||
let (code, v) = conform(&d, &root, "sink", "native-sink", &planted(), &out);
|
||||
assert_eq!(code, 1, "{v:#}");
|
||||
assert_eq!(v["data"]["conformant"], false);
|
||||
let failed: Vec<&str> = v["data"]["failures"].as_array().unwrap().iter().map(|f| f["name"].as_str().unwrap()).collect();
|
||||
assert!(failed.contains(&"tampered-fragment"), "{failed:?}");
|
||||
assert!(!failed.contains(&"signed-vod"), "a session with no refused object still passes: {failed:?}");
|
||||
// The record is a genuine record of a run that did not pass: it verifies, and admits nothing.
|
||||
let o = wai(&d, &["verify", out.to_str().unwrap()]);
|
||||
assert!(o.status.success() && text(&o).contains("not conformant"), "{}", text(&o));
|
||||
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), out.to_str().unwrap()]);
|
||||
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
||||
assert!(text(&o).contains("FAIL admission rule 2 not_conformant"), "{}", text(&o));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sink_run_against_a_corpus_changed_to_suit_it_is_refused() {
|
||||
let d = tmp("changed");
|
||||
let root = wai_dir();
|
||||
// A copy of what a run reads, its answers changed to the planted sink's.
|
||||
let copy = d.join("wai");
|
||||
std::fs::create_dir_all(copy.join("registry")).unwrap();
|
||||
std::fs::create_dir_all(copy.join("live-conformance/native-sink")).unwrap();
|
||||
for f in ["SPEC.md", "CHANGELOG.md", "registry/capabilities.json"] {
|
||||
std::fs::copy(root.join(f), copy.join(f)).unwrap();
|
||||
}
|
||||
let mut corpus: Value = serde_json::from_slice(&std::fs::read(root.join("live-conformance/native-sink/vectors.json")).unwrap()).unwrap();
|
||||
for c in corpus["cases"].as_array_mut().unwrap() {
|
||||
for call in c["calls"].as_array_mut().unwrap() {
|
||||
if call["answer"]["refused"] == "object_digest_mismatch" {
|
||||
call["answer"]["present"] = Value::Bool(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::fs::write(copy.join("live-conformance/native-sink/vectors.json"), serde_json::to_vec_pretty(&corpus).unwrap()).unwrap();
|
||||
let out = d.join("suited.wai-conform.json");
|
||||
let (code, v) = conform(&d, ©, "sink", "native-sink", &planted(), &out);
|
||||
assert_eq!(code, 0, "against the changed corpus the planted sink passes: {v:#}");
|
||||
let o = wai(&d, &["verify", "--conformant", "--root", copy.to_str().unwrap(), out.to_str().unwrap()]);
|
||||
assert!(o.status.success(), "a relying party holding the changed corpus admits it: {}", text(&o));
|
||||
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), out.to_str().unwrap()]);
|
||||
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
||||
assert!(text(&o).contains("FAIL admission rule 4 corpus_mismatch"), "{}", text(&o));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_record_of_a_suite_this_build_does_not_run_is_refused() {
|
||||
let d = tmp("unknown");
|
||||
let root = wai_dir();
|
||||
let out = d.join("native-sink.wai-conform.json");
|
||||
let (code, _) = conform(&d, &root, "sink", "native-sink", &native(false), &out);
|
||||
assert_eq!(code, 0);
|
||||
let rec: Value = serde_json::from_slice(&std::fs::read(&out).unwrap()).unwrap();
|
||||
let mut r = rec.as_object().unwrap().clone();
|
||||
r.remove("sig");
|
||||
r.remove("signer_pubkey");
|
||||
r.insert("suite".into(), Value::from("nonesuch"));
|
||||
let key = ed25519_dalek::SigningKey::from_bytes(&[0x45; 32]);
|
||||
let r = wai::conform_result::sign(r, &key).unwrap();
|
||||
let other = d.join("nonesuch.wai-conform.json");
|
||||
std::fs::write(&other, serde_json::to_vec_pretty(&Value::Object(r)).unwrap()).unwrap();
|
||||
assert!(wai(&d, &["verify", other.to_str().unwrap()]).status.success(), "signed again, it verifies");
|
||||
let o = wai(&d, &["verify", "--conformant", "--root", root.to_str().unwrap(), other.to_str().unwrap()]);
|
||||
assert_eq!(o.status.code(), Some(1), "{}", text(&o));
|
||||
assert!(text(&o).contains("FAIL admission rule 3 suite_unknown"), "{}", text(&o));
|
||||
// --root alone is a usage error: it names the corpus --conformant compares with.
|
||||
assert_eq!(wai(&d, &["verify", "--root", root.to_str().unwrap(), out.to_str().unwrap()]).status.code(), Some(2));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
//! object, its receipt index and its group's receipt file, and `wai verify` checks them
|
||||
//! offline and refuses each when it is altered; `wai create` writes each example of
|
||||
//! `wai/examples/` as a project, file for file and byte for byte, and never into a directory
|
||||
//! that holds anything.
|
||||
//! that holds anything. `wai verify` also reads the envelopes under a path: a planted envelope
|
||||
//! that pins nothing where its capability decodes against a parameter set is reported
|
||||
//! `prior_unpinned`, and still verifies; one whose pin breaks SPEC §3.1 does not.
|
||||
#![cfg(feature = "quickstart")]
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
|
@ -42,7 +44,8 @@ fn hello_runs_the_four_steps_and_verify_checks_what_it_wrote() {
|
|||
let h = d.join("h");
|
||||
let v = wai(&d, &["verify", "h"]);
|
||||
assert!(v.status.success(), "{}", text(&v));
|
||||
assert!(text(&v).contains("delivered-verified") && text(&v).contains("1 receipt index verified"), "{}", text(&v));
|
||||
// The object is an envelope there too, and its pins are read.
|
||||
assert!(text(&v).contains("delivered-verified") && text(&v).contains("1 receipt index and 1 envelope verified"), "{}", text(&v));
|
||||
|
||||
// A byte of the object changed: its content hash is not the receipt's.
|
||||
let obj = one(&h, ".wai");
|
||||
|
|
@ -77,6 +80,49 @@ fn hello_runs_the_four_steps_and_verify_checks_what_it_wrote() {
|
|||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
/// SPEC §3.1, *Pin by default*: `wai verify` reports `prior_unpinned` on a planted envelope
|
||||
/// that pins nothing over a capability that decodes against a parameter set, and the envelope
|
||||
/// still verifies; it reports nothing on one over a standard codec or on a pinned one; and it
|
||||
/// refuses one whose pin a verifier can tell is wrong without holding a set.
|
||||
#[test]
|
||||
fn verify_reports_an_unpinned_envelope() {
|
||||
use wai::container::{Conditioning, Manifest, ModelRequirement, Prior, Wai};
|
||||
let d = tmp("unpinned");
|
||||
let p = d.join("planted");
|
||||
std::fs::create_dir_all(&p).unwrap();
|
||||
std::fs::write(d.join("payload.bin"), b"opaque test bytes").unwrap();
|
||||
let wrap = |cap: &str, out: &str| {
|
||||
let o = wai(&d, &["wrap", cap, "payload.bin", "-o", out, "--intent", "improve"]);
|
||||
assert!(o.status.success(), "{}", text(&o));
|
||||
o
|
||||
};
|
||||
let w = wrap("wai.neural.bmshj2018", "planted/unpinned.wai");
|
||||
assert!(text(&w).contains("note prior_unpinned: wai.neural.bmshj2018"), "wrap says it pinned nothing: {}", text(&w));
|
||||
wrap("wai.image.png", "planted/png.wai");
|
||||
let pin = Prior::new("int_mlicpp/test", "7ef44f3b1fc4a2df6c3e3b68460f595cae3d31cb7f9018699e29e738340272a4", "int-rans/p16+int-mlicpp/i64");
|
||||
let pinned = Wai::new(Manifest::new("image", "improve", ModelRequirement::new("wai.neural.int_mlicpp"),
|
||||
Conditioning::new("int_mlicpp").with_prior(pin.clone())), b"M".to_vec());
|
||||
std::fs::write(p.join("pinned.wai"), pinned.to_bytes().unwrap()).unwrap();
|
||||
|
||||
let v = wai(&d, &["verify", "planted"]);
|
||||
let t = text(&v);
|
||||
assert!(v.status.success(), "a report is not a refusal: {t}");
|
||||
assert!(t.contains("note prior_unpinned: wai.neural.bmshj2018 decodes against a parameter set"), "{t}");
|
||||
assert_eq!(t.matches("prior_unpinned").count(), 1, "the PNG and the pinned envelope are not reported: {t}");
|
||||
assert!(t.contains("3 envelopes verified"), "{t}");
|
||||
let v = wai(&d, &["verify", "planted/unpinned.wai", "--json"]);
|
||||
assert!(v.status.success() && text(&v).contains("prior_unpinned"), "{}", text(&v));
|
||||
|
||||
// A pin over a capability that reads no set: the envelope does not conform.
|
||||
let bad = Wai::new(Manifest::new("image", "replicate", ModelRequirement::new("wai.image.png"),
|
||||
Conditioning::new("png").with_prior(pin)), b"PNG".to_vec());
|
||||
std::fs::write(p.join("bad.wai"), bad.to_bytes().unwrap()).unwrap();
|
||||
let v = wai(&d, &["verify", "planted"]);
|
||||
assert_eq!(v.status.code(), Some(1), "{}", text(&v));
|
||||
assert!(text(&v).contains("FAIL WAI1 wai.image.png: prior_not_applicable"), "{}", text(&v));
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
}
|
||||
|
||||
/// Every file of an example but its `test/` directory, relative to it, sorted.
|
||||
fn example_files(dir: &Path) -> Vec<String> {
|
||||
fn walk(root: &Path, p: &Path, out: &mut Vec<String>) {
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue