wai: the energy order of dispatch, a self-metered relay, HwShunt closed to new profiles, meters that seal what they read, and what fewer bytes do not save (EN-C) #463
479 changed files with 22543 additions and 1144 deletions
26
.github/workflows/joule-code-conformance.yml
vendored
26
.github/workflows/joule-code-conformance.yml
vendored
|
|
@ -235,6 +235,32 @@ jobs:
|
|||
shell: bash
|
||||
run: cargo test --locked -p wtransport --lib
|
||||
|
||||
# The relay's own energy on a counter (WAI live-metering §8): tests/relay_energy.rs on
|
||||
# macOS, where the relay reads its process's per-task CPU energy unprivileged. The test
|
||||
# is required here (JOULE_MOQ_RELAY_ENERGY_REQUIRE): a host with no counter fails it
|
||||
# rather than skipping. On linux-arm64 above the same test prints SKIPPED, which is not a
|
||||
# pass of the measurement: that VM has no counter the relay reads.
|
||||
moq-relay-energy:
|
||||
name: joule-moq-relay, its own energy on a counter (macos)
|
||||
runs-on: macos
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: The room's record carries the relay's measured energy, and the marker where it is not the room's
|
||||
working-directory: joule-code/joule-code-rs
|
||||
shell: bash
|
||||
env:
|
||||
JOULE_MOQ_RELAY_ENERGY_REQUIRE: '1'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
cargo test --locked -p joule-moq-relay --test relay_energy -- --nocapture --test-threads=1 2>&1 | tee "$RUNNER_TEMP/relay-energy.log"
|
||||
grep -q "test result: ok. 3 passed; 0 failed" "$RUNNER_TEMP/relay-energy.log"
|
||||
grep -q "measured: .* scope task" "$RUNNER_TEMP/relay-energy.log"
|
||||
! grep -q "SKIPPED" "$RUNNER_TEMP/relay-energy.log"
|
||||
|
||||
# The WHIP and WHEP gateway. Every test is on loopback, against a relay that checks
|
||||
# every receipt at its door: a WHIP publisher (str0m as the client) of H.264 and Opus
|
||||
# whose frames a relay subscriber receives whole with every receipt verified, and DELETE
|
||||
|
|
|
|||
35
.github/workflows/wai-ops-gpu.yml
vendored
35
.github/workflows/wai-ops-gpu.yml
vendored
|
|
@ -5,19 +5,25 @@ name: wai-ops-gpu integer operators on GPUs
|
|||
# (64- and 128-bit arithmetic built from 32-bit words), and the integer decoders with
|
||||
# their graphs on the device (wai-rs `ops_gpu`).
|
||||
#
|
||||
# - metal (the `macos` runner, whose host has a GPU): the arithmetic probe on a
|
||||
# - metal (the `macos` runner, whose host has a GPU): WGSL's integer semantics
|
||||
# (wai/wgsl-conformance, determinism-tiers §5, Claiming the tier on a GPU), the
|
||||
# committed vectors and SplitMix64 cases of every operation, with an edited kernel
|
||||
# refused at discovery; the arithmetic probe on a
|
||||
# million cases; every graph vector of wai/ops-conformance that the subset covers,
|
||||
# at both widths, against the committed results and the interpreter; the shader
|
||||
# mutants, every one of which the corpus must catch; every committed vector and
|
||||
# refusal fixture of every capability that runs a wai-ops/1 graph, the jpegai/v1
|
||||
# inputs included, against the decode on the CPU; and the corpus again in a
|
||||
# headless browser's WebGPU, the kernels compiled by the browser's own WGSL
|
||||
# compiler. WAI_OPS_GPU_EXPECT declares the one adapter the host must offer, and
|
||||
# compiler, with the semantics suite there and its vectors held to the reference
|
||||
# compiled to wasm32, in the page and under Node. WAI_OPS_GPU_EXPECT declares the one adapter the host must offer, and
|
||||
# WAI_OPS_GPU_REQUIRE makes an absent adapter a failure, never a skip; each step
|
||||
# then requires the report lines with their counts, so a filtered or empty run
|
||||
# fails too.
|
||||
# - checks (`linux`, a container with no GPU): the crate and its tests build, the
|
||||
# device tests print SKIPPED and pass, and the same tests under
|
||||
# - checks (`linux`, a container with no GPU): the crate and its tests build; the
|
||||
# WGSL rules on naga's IR (tests/wgsl_rules.rs) and the committed semantics vectors
|
||||
# against the reference, which need no adapter, pass; the device tests print SKIPPED
|
||||
# and pass, and the same tests under
|
||||
# WAI_OPS_GPU_REQUIRE=1 fail: an absent adapter is not a pass. The browser module
|
||||
# builds for wasm32-unknown-unknown; the bench tool builds.
|
||||
#
|
||||
|
|
@ -36,6 +42,7 @@ on:
|
|||
- 'wai/wai-ops-gpu/**'
|
||||
- 'wai/wai-ops/**'
|
||||
- 'wai/ops-conformance/**'
|
||||
- 'wai/wgsl-conformance/**'
|
||||
# The decoders whose graphs run on the device, and what they decode.
|
||||
- 'wai/wai-rs/Cargo.toml'
|
||||
- 'wai/wai-rs/Cargo.lock'
|
||||
|
|
@ -120,7 +127,7 @@ jobs:
|
|||
fi
|
||||
else
|
||||
grep -q '^ops-gpu corpus: SKIPPED' "$rep" || { echo "::error::the corpus test printed neither a result nor SKIPPED"; exit 1; }
|
||||
if WAI_OPS_GPU_REQUIRE=1 cargo test --locked --release --test corpus --test arith --test mutants > "$RUNNER_TEMP/require.log" 2>&1; then
|
||||
if WAI_OPS_GPU_REQUIRE=1 cargo test --locked --release --test corpus --test arith --test mutants --test semantics > "$RUNNER_TEMP/require.log" 2>&1; then
|
||||
echo "::error::with no adapter, the device tests passed under WAI_OPS_GPU_REQUIRE=1"; exit 1
|
||||
fi
|
||||
grep -q 'WAI_OPS_GPU_REQUIRE is set' "$RUNNER_TEMP/require.log" \
|
||||
|
|
@ -165,16 +172,21 @@ jobs:
|
|||
: > "$WAI_OPS_GPU_REPORT"
|
||||
nice -n 10 cargo test --locked --release -- --nocapture --test-threads=1 2>&1 | tee "$log"
|
||||
k=gpu-metal-0
|
||||
# Six tests in four binaries: the library's one, arith's two, corpus's two and
|
||||
# mutants' one, none filtered, ignored or failed.
|
||||
# Sixteen tests in six binaries: the library's four, arith's two, corpus's two,
|
||||
# mutants' one, semantics' three and wgsl_rules' four, none filtered, ignored or
|
||||
# failed.
|
||||
ran=$(awk '/^test result: ok\. [0-9]+ passed; 0 failed; 0 ignored; 0 measured; 0 filtered out/ { n += $4 } END { print n + 0 }' "$log")
|
||||
[ "$ran" = 6 ] || { echo "::error::$ran tests ran and passed, not 6"; exit 1; }
|
||||
[ "$ran" = 16 ] || { echo "::error::$ran tests ran and passed, not 16"; exit 1; }
|
||||
! grep -q '^test result: FAILED' "$log" || { echo "::error::a test failed"; exit 1; }
|
||||
grep -qxF "ops-gpu adapters: as declared ($k)" "$WAI_OPS_GPU_REPORT" || { echo "::error::the adapters found are not $k"; exit 1; }
|
||||
grep -qE "^ops-gpu arith: key=$k .* cases=[0-9]+ words=[0-9]+ equal$" "$WAI_OPS_GPU_REPORT" || { echo "::error::no probe result for $k"; exit 1; }
|
||||
grep -qE "^ops-gpu corpus: key=$k .* acc32 vectors=[1-9][0-9]* decodes=[1-9][0-9]*; acc64 vectors=[1-9][0-9]* decodes=[1-9][0-9]*; .* mismatches=0 errors=0 " "$WAI_OPS_GPU_REPORT" \
|
||||
|| { echo "::error::no equal corpus run for $k"; exit 1; }
|
||||
grep -qxF "ops-gpu mutants: key=$k killed 18 of 18" "$WAI_OPS_GPU_REPORT" || { echo "::error::a mutant survived on $k, or the mutant test did not run"; exit 1; }
|
||||
grep -qE "^ops-gpu semantics: key=$k .* committed=[1-9][0-9]* cases=[1-9][0-9]* guarded=[1-9][0-9]* differ=0 " "$WAI_OPS_GPU_REPORT" \
|
||||
|| { echo "::error::no run of WGSL's integer semantics on $k with every guarded case equal"; exit 1; }
|
||||
grep -qE "^ops-gpu semantics mutant: key=$k refused: the WGSL semantics suite: rem_i32 " "$WAI_OPS_GPU_REPORT" \
|
||||
|| { echo "::error::the edited semantics kernel was not refused on $k"; exit 1; }
|
||||
- name: The jpegai/v1 inputs
|
||||
shell: bash
|
||||
run: bash wai/tools/wai_jpegai_fetch_e2e_inputs.sh "$RUNNER_TEMP/jpegai"
|
||||
|
|
@ -238,7 +250,14 @@ jobs:
|
|||
nice -n 10 cargo build --locked --release --target wasm32-unknown-unknown
|
||||
t="${CARGO_TARGET_DIR:-target}"
|
||||
wasm-bindgen --target web --out-dir pkg --out-name wai_ops_gpu_browser "$t/wasm32-unknown-unknown/release/wai_ops_gpu_browser.wasm"
|
||||
# The committed semantics vectors against the reference compiled to wasm32,
|
||||
# under Node, with no GPU.
|
||||
node semantics_reference.mjs
|
||||
log="$RUNNER_TEMP/browser.log"
|
||||
nice -n 10 node run.mjs 2>&1 | tee "$log"
|
||||
grep -qE '^browser webgpu: key=gpu-webgpu-[0-9a-f]+ .* acc32 vectors=[1-9][0-9]* decodes=[1-9][0-9]*; acc64 vectors=[1-9][0-9]* decodes=[1-9][0-9]*; .* mismatches=0 errors=0$' "$log" \
|
||||
|| { echo "::error::no equal corpus run on a GPU adapter in the browser"; exit 1; }
|
||||
grep -qE '^wasm reference: target=wasm32 .* cases=[1-9][0-9]* equal$' "$log" \
|
||||
|| { echo "::error::the semantics vectors are not the wasm32 reference's in the page"; exit 1; }
|
||||
grep -qE '^browser webgpu semantics: key=gpu-webgpu-[0-9a-f]+ .* guarded=[1-9][0-9]* differ=0 ' "$log" \
|
||||
|| { echo "::error::no run of WGSL's integer semantics on a GPU adapter in the browser with every guarded case equal"; exit 1; }
|
||||
|
|
|
|||
101
.github/workflows/wai-pack.yml
vendored
101
.github/workflows/wai-pack.yml
vendored
|
|
@ -6,7 +6,11 @@ name: wai-pack
|
|||
# - legs: on each architecture of the fleet, clippy with float arithmetic denied (no
|
||||
# float decides anything in a package), the tests in release with a floor on how many
|
||||
# each target lists, and the corpus recomputed by the crate: the golden packages
|
||||
# packed again byte for byte and verified, every mutation's reason.
|
||||
# packed again byte for byte and verified, every mutation's reason. On the macOS leg,
|
||||
# whose host has a GPU, the steps a GPU runs (packaging §11.6): wai-pack-gpu's kernels
|
||||
# against their references and every edited kernel caught, and wai-pack's fabric
|
||||
# differential (every golden packed on the GPU byte for byte, every verdict the CPU's),
|
||||
# an absent adapter a failure there (WAI_PACK_GPU_REQUIRE), never a skip.
|
||||
# - independent: the corpus recomputed by its second implementation, written in Python
|
||||
# from the extension text and sharing no code with wai-pack, the golden packages
|
||||
# verified and re-derived from their sources among it (every refinement layer and
|
||||
|
|
@ -20,7 +24,8 @@ name: wai-pack
|
|||
#
|
||||
# Push filters match the push's delta, so every path a build or a verifier reads is
|
||||
# listed: the crate, the wai crate it builds on, the corpus, the BLAKE3, integer payload,
|
||||
# staged-delivery and staged-measurement verifiers and the registry export pack_verify.py
|
||||
# staged-delivery and staged-measurement verifiers, the claim verifier (a metered
|
||||
# package's claim) and the OER verifier it imports, the registry export pack_verify.py
|
||||
# reads, and this file.
|
||||
|
||||
concurrency:
|
||||
|
|
@ -31,10 +36,13 @@ on:
|
|||
pull_request:
|
||||
paths:
|
||||
- 'wai/wai-pack/**'
|
||||
- 'wai/wai-pack-gpu/**'
|
||||
- 'wai/wai-rs/**'
|
||||
- 'wai/pack-conformance/**'
|
||||
- 'wai/claim-conformance/_blake3.py'
|
||||
- 'wai/claim-conformance/_ed25519.py'
|
||||
- 'wai/claim-conformance/claim_verify.py'
|
||||
- 'wai/oer-conformance/oer2_verify.py'
|
||||
- 'wai/int-payload-conformance/int_payload_verify.py'
|
||||
- 'wai/staged-conformance/staged_verify.py'
|
||||
- 'wai/staged-measure-conformance/sim_verify.py'
|
||||
|
|
@ -126,11 +134,17 @@ jobs:
|
|||
echo "$target: $n tests (floor $floor)"
|
||||
[ "$n" -ge "$floor" ] || { echo "::error::$target lists $n tests, under its floor of $floor"; fail=1; }
|
||||
done <<'EOF'
|
||||
--lib 34
|
||||
--lib 39
|
||||
--test=cmaf 4
|
||||
--test=conformance 1
|
||||
--test=packs 7
|
||||
--test=fabric 3
|
||||
--test=floor_only 2
|
||||
--test=ladder 3
|
||||
--test=pack_alloc 1
|
||||
--test=packs 8
|
||||
--test=review 9
|
||||
--test=refusals 5
|
||||
--test=refusals 6
|
||||
--test=repack 3
|
||||
--test=scenecut 3
|
||||
--test=source_alloc 1
|
||||
--test=staged 10
|
||||
|
|
@ -141,6 +155,36 @@ jobs:
|
|||
working-directory: wai/wai-pack
|
||||
run: cargo run --release --locked --bin wai-pack -- conformance check ../pack-conformance
|
||||
|
||||
# The steps a GPU runs (packaging §11.6), on the one leg whose host has a GPU. Each
|
||||
# test prints the adapter it ran on; the step requires those lines, so a run that
|
||||
# found no adapter, or filtered the tests away, fails.
|
||||
- name: wai-pack-gpu, clippy and its kernels against their references (Metal)
|
||||
if: matrix.os == 'macos'
|
||||
shell: bash
|
||||
working-directory: wai/wai-pack-gpu
|
||||
env:
|
||||
WAI_PACK_GPU_REQUIRE: '1'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo clippy --locked --all-targets -- -D warnings
|
||||
cargo test --release --locked -- --nocapture 2>&1 | tee "$RUNNER_TEMP/gpu.txt"
|
||||
grep -q 'motion search, 54 frames, every vector the text' "$RUNNER_TEMP/gpu.txt"
|
||||
grep -q 'every hash the reference crate' "$RUNNER_TEMP/gpu.txt"
|
||||
[ "$(grep -c '^caught: ' "$RUNNER_TEMP/gpu.txt")" -ge 12 ]
|
||||
- name: wai-pack's fabric differential on the GPU (Metal)
|
||||
if: matrix.os == 'macos'
|
||||
shell: bash
|
||||
working-directory: wai/wai-pack
|
||||
env:
|
||||
WAI_PACK_GPU_REQUIRE: '1'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo clippy --locked --all-targets --features gpu,fabric-meter -- -D warnings
|
||||
cargo test --release --locked --features gpu --test fabric -- --nocapture 2>&1 | tee "$RUNNER_TEMP/fabric.txt"
|
||||
grep -q 'the search on the GPU: ' "$RUNNER_TEMP/fabric.txt"
|
||||
grep -q 'verdicts, each the CPU' "$RUNNER_TEMP/fabric.txt"
|
||||
grep -q 'test result: ok. 7 passed' "$RUNNER_TEMP/fabric.txt"
|
||||
|
||||
independent:
|
||||
name: wai-pack · independent verifier (Python)
|
||||
needs: relevant
|
||||
|
|
@ -163,6 +207,12 @@ jobs:
|
|||
working-directory: wai
|
||||
shell: bash
|
||||
run: python3 pack-conformance/pack_verify.py pack-conformance
|
||||
# The same checks with the hashes and decodes run ahead in worker processes
|
||||
# (--jobs): every check and its reason are the serial run's.
|
||||
- name: pack_verify.py --jobs 2
|
||||
working-directory: wai
|
||||
shell: bash
|
||||
run: python3 pack-conformance/pack_verify.py --jobs 2 pack-conformance
|
||||
- name: Install Rust 1.98.0
|
||||
shell: bash
|
||||
run: |
|
||||
|
|
@ -207,6 +257,13 @@ jobs:
|
|||
set -euo pipefail
|
||||
python3 ../pack-conformance/stage_diff.py target/debug/wai-pack 1 1000
|
||||
python3 ../pack-conformance/stage_diff.py target/release/wai-pack 2 3000
|
||||
# The supplied CMAF reader differentially (packaging §7.7): seeded alterations of a
|
||||
# media muxer's segments and of §7.6's writer's, every verdict and accepted segment's
|
||||
# samples of wai-pack's reader equal to pack_verify.py's.
|
||||
- name: cmaf_diff.py, wai-pack against pack_verify.py
|
||||
shell: bash
|
||||
working-directory: wai/wai-pack
|
||||
run: python3 ../pack-conformance/cmaf_diff.py target/release/wai-pack 1 3000
|
||||
# A still's reader differentially: the zlib library's own streams, dynamic blocks
|
||||
# built on each of RFC 1951's and zlib's code-set rules, and their alterations, every
|
||||
# verdict of wai-pack's reader equal to pack_verify.py's, which inflates with
|
||||
|
|
@ -232,27 +289,31 @@ jobs:
|
|||
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
|
||||
$SUDO apt-get update -qq
|
||||
$SUDO apt-get install -y -qq ffmpeg
|
||||
# Test 14: the golden's init.mp4 and its fragments, in its floor playlist's order,
|
||||
# read as one AV1 track of every frame of the source, with a key frame at each piece
|
||||
# start of the plan and nowhere else, and decode without an error.
|
||||
# Test 14: each golden floor's init.mp4 and its fragments, in its floor playlist's
|
||||
# order, read as one AV1 track of every frame of the source, with a key frame at each
|
||||
# piece start of the plan and nowhere else, and decode without an error; a ladder
|
||||
# golden has one floor playlist per floor rung (packaging §7.1), each read so.
|
||||
- name: The goldens' floors read and decode
|
||||
shell: bash
|
||||
working-directory: wai/pack-conformance/packs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
for g in video-base-01 video-staged-01; do
|
||||
for g in video-base-01 video-staged-01 video-ladder-01 video-cmaf-01 video-floor-only-02; do
|
||||
( cd "$g/package"
|
||||
tmp=$(mktemp -d)
|
||||
cat floor/v/init.mp4 $(grep -v '^#' hls/classic/floor-v.m3u8 | sed 's#^\.\./\.\./##') > "$tmp/floor.mp4"
|
||||
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=codec_name,width,height,nb_read_frames -of default=nw=1 "$tmp/floor.mp4" | tee "$tmp/probe.txt"
|
||||
grep -qx 'codec_name=av1' "$tmp/probe.txt"
|
||||
grep -qx 'nb_read_frames=200' "$tmp/probe.txt"
|
||||
keys=$(ffprobe -v error -select_streams v:0 -show_entries packet=pts,flags -of csv=p=0 "$tmp/floor.mp4" | awk -F, '$2 ~ /K/ {print $1}' | tr '\n' ' ')
|
||||
want=$(tr '\n' ' ' < plan-v.txt)
|
||||
echo "$g key frames: $keys"
|
||||
[ "$keys" = "$want" ] || { echo "::error::$g: key frames at $keys; the plan says $want"; exit 1; }
|
||||
ffmpeg -v error -xerror -i "$tmp/floor.mp4" -f null -
|
||||
echo "$g: the floor reads as one AV1 track of 200 frames, a key frame at each piece start" )
|
||||
for pl in hls/classic/floor-*.m3u8; do
|
||||
f=$(basename "$pl" .m3u8); f=${f#floor-}
|
||||
tmp=$(mktemp -d)
|
||||
cat "floor/$f/init.mp4" $(grep -v '^#' "$pl" | sed 's#^\.\./\.\./##') > "$tmp/floor.mp4"
|
||||
ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=codec_name,width,height,nb_read_frames -of default=nw=1 "$tmp/floor.mp4" | tee "$tmp/probe.txt"
|
||||
grep -qx 'codec_name=av1' "$tmp/probe.txt"
|
||||
grep -qx 'nb_read_frames=200' "$tmp/probe.txt"
|
||||
keys=$(ffprobe -v error -select_streams v:0 -show_entries packet=pts,flags -of csv=p=0 "$tmp/floor.mp4" | awk -F, '$2 ~ /K/ {print $1}' | tr '\n' ' ')
|
||||
want=$(tr '\n' ' ' < plan-v.txt)
|
||||
echo "$g $f key frames: $keys"
|
||||
[ "$keys" = "$want" ] || { echo "::error::$g $f: key frames at $keys; the plan says $want"; exit 1; }
|
||||
ffmpeg -v error -xerror -i "$tmp/floor.mp4" -f null -
|
||||
echo "$g $f: the floor reads as one AV1 track of 200 frames, a key frame at each piece start"
|
||||
done )
|
||||
done
|
||||
|
||||
licences:
|
||||
|
|
|
|||
13
.github/workflows/wai-web-wasm.yml
vendored
13
.github/workflows/wai-web-wasm.yml
vendored
|
|
@ -135,6 +135,19 @@ jobs:
|
|||
cargo build --locked --release --target wasm32-unknown-unknown
|
||||
wasm-bindgen --target nodejs --out-dir demo/pkg-node --out-name wai_web \
|
||||
target/wasm32-unknown-unknown/release/wai_web.wasm
|
||||
# The platform bundle (feature platform_decode: platform-decode's query, the answer in
|
||||
# browser-sink's report, and browser-sink §4.7's choice), a bundle of its own that the
|
||||
# main one does not carry, so nothing above builds it.
|
||||
- name: Build wai-web's platform bundle for wasm32 (feature platform_decode)
|
||||
shell: bash
|
||||
working-directory: wai/wai-web
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo build --locked --release --target wasm32-unknown-unknown --features platform_decode
|
||||
wasm-bindgen --target web --out-dir demo/pkg-platform --out-name wai_platform_decode \
|
||||
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 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
|
||||
|
|
|
|||
430
docs/research/wai-cross-fabric.md
Normal file
430
docs/research/wai-cross-fabric.md
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
# WAI across fabrics: research for the packer and the verifiers
|
||||
|
||||
Research and measurements of 2026-10-22, for the change that lets each step of a WAI
|
||||
pack or verify run on the fabric that does it with the least measured energy (CPU, GPU,
|
||||
a hardware media engine, or a neural engine): [packaging](../../wai/extensions/packaging.md)
|
||||
§11.6, [jwp-receipts](../../wai/extensions/jwp-receipts.md) §2.4.8 and
|
||||
[energy-measurement](../../wai/extensions/energy-measurement.md) §6.3.
|
||||
|
||||
**Method.** Every claim about a product, an API or a specification links the page it
|
||||
was checked against, fetched on the date above. Where a vendor publishes nothing, the
|
||||
entry says *unconfirmed*. Items marked **[measured]** were run on the fleet for this
|
||||
document (commands in §9); items marked **[local]** were read from the macOS 27 SDK on a
|
||||
fleet machine. Exact counts and digests are exact; times and energies are indicative,
|
||||
from a shared host whose load is given beside each.
|
||||
|
||||
## 1. The fleet, as probed
|
||||
|
||||
Each Mac was queried read-only over SSH (`sw_vers`, `sysctl machdep.cpu.brand_string
|
||||
hw.memsize`, `system_profiler SPDisplaysDataType`) **[measured]**:
|
||||
|
||||
| Host | Chip | GPU cores | Memory | OS | AV1 decode engines | AV1 encode |
|
||||
|---|---|---:|---:|---|---:|---|
|
||||
| build host (`100.100.141.123`) | M3 Ultra | 80 | 256 GB | macOS 27.0.1 | 2 | none |
|
||||
| `.3` (`10.10.10.3`) | M3 Ultra | 60 | 96 GB | macOS 27.0.1 | 2 | none |
|
||||
| `.1` (`100.98.101.113`) | M4 Max | 40 | 128 GB | macOS 27.0.1 | 1 | none |
|
||||
| prod (`100.64.224.0`) | M4 Max | 32 | 36 GB | macOS 27.0.1 | 1 | none |
|
||||
| dev | M5 Max | | | macOS 27.0.1 | 1 | none |
|
||||
|
||||
The engine counts are Apple's: M3 Ultra "Two video decode engines · Four video encode
|
||||
engines · Four ProRes encode and decode engines · AV1 decode", M4 Max "Video decode
|
||||
engine · Two video encode engines · Two ProRes encode and decode engines · AV1 decode"
|
||||
(https://support.apple.com/en-us/122211), M5 Max the same as M4 Max
|
||||
(https://www.apple.com/macbook-pro/specs/). Every Mac reports Metal 4.
|
||||
|
||||
- **cbio-device** (Windows, x86_64), queried with `Get-CimInstance Win32_VideoController`
|
||||
**[measured]**: an Intel Iris Xe (PCI `8086:9A49`, Tiger Lake) and an NVIDIA GeForce
|
||||
RTX 2060 (PCI `10DE:1F15`, Turing), on an i7-1165G7. Turing has neither AV1 decode nor
|
||||
AV1 encode in NVDEC/NVENC (§3.2); Tiger Lake decodes AV1, with a film-grain path that is
|
||||
not bit-exact (§6).
|
||||
- **jetson-hub** (Ubuntu, x86_64) was not probed: its SSH goes through Tailscale SSH,
|
||||
which asked for an interactive browser check that this work did not perform. The
|
||||
repository's own record (wai/extensions/determinism-tiers.md §5) lists a Linux x86_64
|
||||
host with a discrete GPU of PCI `10de:28e1` and an integrated `8086:a7a0` and another
|
||||
with `1002:1586`; which hosts those are was not confirmed.
|
||||
|
||||
## 2. The steps, and the fabrics that can run each
|
||||
|
||||
| Step | CPU | GPU | Media engine | Neural engine | Placed on |
|
||||
|---|---|---|---|---|---|
|
||||
| `source-decode` (a coded source to frames, before a pack) | dav1d | | AV1 decode, M3 and later (VideoToolbox); NVDEC Ampere+; QSV Tiger Lake+; VCN 3+ | | either, on equal pictures (§6) |
|
||||
| `source-read`, `segment` | yes | | | | CPU (sequential, cheap) |
|
||||
| `base-search` (the integer motion search) | reference | exact integer kernel | none (no hardware runs WAI's search; VideoToolbox's motion estimation is not the encoder's search, §3.1) | unsound (FP16, §5) | GPU where it qualifies |
|
||||
| `base-code`, `refine-code` (residuals, rANS) | reference | | none: no media engine codes WIV1 or WIR1 | | CPU (rANS is sequential) |
|
||||
| `sse` | reference | exact (partial sums under 2^32) | | unsound | by measured cost |
|
||||
| `object-hash` (BLAKE3) | reference | exact (chunks on the device, parents on the host) | | | by measured cost |
|
||||
| `digest` (SHA-256) | yes | no: one message is one chain (FIPS 180-4 §6.2.2, https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.180-4.pdf) | | | CPU |
|
||||
| `floor-read` (AV1 and CMAF structure) | yes | | | | CPU |
|
||||
| `floor-decode` | dav1d | | AV1 decode, as above | | informative only (§6.3) |
|
||||
| `index`, `io` | yes | | | | CPU |
|
||||
|
||||
What this decides: the GPU takes the one step that dominates a pack's work (the motion
|
||||
search: at least 96.8% of phase 0's CPU time, §9.2), plus SSE and BLAKE3 where the
|
||||
measured cost says so, which on the build host it does not (§9.3). No hardware encoder computes any WAI step. The media engine decodes AV1 sources and
|
||||
floors. The neural engine computes nothing exact.
|
||||
|
||||
## 3. Media engines
|
||||
|
||||
### 3.1 Apple: VideoToolbox
|
||||
|
||||
- **AV1 decode starts with M3 and A17 Pro.** "the media engine supports AV1 decoding,
|
||||
enabling power-efficient playback of streaming services"
|
||||
(https://www.apple.com/newsroom/2023/10/apple-unveils-m3-m3-pro-and-m3-max-the-most-advanced-chips-for-a-personal-computer/);
|
||||
M1 and M2 spec pages list no AV1 (https://support.apple.com/kb/SP825,
|
||||
https://support.apple.com/kb/SP869).
|
||||
- **No Apple chip encodes AV1 in hardware**, through the 2026 launches: every spec page
|
||||
and press release says "AV1 decode" only. M5 Pro and M5 Max: "Apple's latest Media
|
||||
Engine with support for hardware-accelerated H.264 and HEVC, AV1 decode, and ProRes
|
||||
encode"
|
||||
(https://www.apple.com/newsroom/2026/03/apple-debuts-m5-pro-and-m5-max-to-supercharge-the-most-demanding-pro-workflows/);
|
||||
M6 and M5 Ultra: "hardware-accelerated AV1 decode"
|
||||
(https://www.apple.com/newsroom/2026/08/apple-introduces-m6-and-m5-ultra-for-a-big-leap-in-performance-and-ai-compute/).
|
||||
VideoToolbox's compression headers name no AV1 encoder **[local]**, and FFmpeg's
|
||||
VideoToolbox encoders are `h264_videotoolbox`, `hevc_videotoolbox` and
|
||||
`prores_videotoolbox` only
|
||||
(https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/videotoolboxenc.c).
|
||||
- **API.** `kCMVideoCodecType_AV1 = 'av01'` **[local]**;
|
||||
`kVTVideoDecoderSpecification_RequireHardwareAcceleratedVideoDecoder`: "If it cannot, it
|
||||
returns an error and the session creation fails"
|
||||
(https://developer.apple.com/documentation/videotoolbox/kvtvideodecoderspecification_requirehardwareacceleratedvideodecoder).
|
||||
A session needs an `av1C` atom carrying the sequence header OBU
|
||||
(https://developer.apple.com/forums/thread/739953). FFmpeg's `av1_videotoolbox` hwaccel
|
||||
was added in commit `f9c5c53` on 2024-10-02 and first released in FFmpeg 8.0
|
||||
(https://github.com/FFmpeg/FFmpeg/commit/f9c5c5358cfef3847674c6f3b3ded9611ebc5647); it
|
||||
requires hardware for AV1 (`videotoolbox.c`,
|
||||
https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/videotoolbox.c).
|
||||
- **What the media engine gives back [measured]** (FFmpeg 9.0.2, M3 Ultra, macOS 27.0.1):
|
||||
- The big-buck-bunny ladder's four AV1 rungs are Main profile, **10-bit** (each rung's
|
||||
`av1C`). VideoToolbox's hardware frames are P010; `hwdownload,format=nv12` is refused
|
||||
("Invalid output format nv12 for hwframe download").
|
||||
- A low-overhead OBU stream (`-f obu`) does not decode on it: the demuxer gives the
|
||||
session no sequence header up front, and the filter graph fails. The ladder's CMAF
|
||||
(with `av1C`) decodes. So the reference tool decodes floors from the package's CMAF
|
||||
files.
|
||||
- Its raw P010 frames equal, frame by frame (MD5 of each of 240 frames of the 1080p
|
||||
rung), dav1d's planar 10-bit output repacked to P010; but FFmpeg's P010-to-8-bit
|
||||
conversion of them does not equal dav1d's own 10-to-8-bit conversion. That
|
||||
conversion rounds and dithers, so a comparison has to stay at the stream's bit depth
|
||||
or convert exactly.
|
||||
- Earlier runs of this document's test compared `-f framemd5` outputs whose stderr had
|
||||
been piped into the hash; the "difference" they showed was FFmpeg's error text. Every
|
||||
comparison in §9 hashes the output file, not the console.
|
||||
- **Motion estimation in hardware.** `VTMotionEstimationSession` (macOS 26) returns 4×4 or
|
||||
16×16 vectors (https://developer.apple.com/documentation/videotoolbox/vtmotionestimationsessioncreate);
|
||||
Apple documents neither the engine, the cost metric, the search window nor
|
||||
determinism, so it cannot compute §5.5's normative search.
|
||||
|
||||
### 3.2 NVIDIA, Intel, AMD, Vulkan Video
|
||||
|
||||
- **NVIDIA** (https://developer.nvidia.com/video-encode-and-decode-gpu-support-matrix-new):
|
||||
AV1 NVDEC on Ampere and later, AV1 NVENC on Ada Lovelace and Blackwell; Turing has
|
||||
neither (so cbio-device's RTX 2060 has no AV1 engine). NVDEC "runs completely
|
||||
independent of compute/graphics engine"
|
||||
(https://docs.nvidia.com/video-technologies/video-codec-sdk/13.0/nvdec-video-decoder-api-prog-guide/index.html).
|
||||
- **Intel** (https://raw.githubusercontent.com/intel/media-driver/master/README.md): AV1
|
||||
decode from Tiger Lake (Gen12) in the Full-Feature build (on Ubuntu,
|
||||
`intel-media-va-driver-non-free`), AV1 encode from Arc Alchemist and Meteor Lake.
|
||||
- **AMD** (https://raw.githubusercontent.com/torvalds/linux/master/drivers/gpu/drm/amd/amdgpu/nv.c,
|
||||
`soc21.c`): AV1 decode on VCN 3 (RDNA2) except VCN 3.0.33, AV1 encode from VCN 4; AV1
|
||||
on instance 0 only of a dual-VCN part.
|
||||
- **Vulkan Video**: `VK_KHR_video_decode_av1` in Vulkan 1.3.277
|
||||
(https://www.khronos.org/blog/khronos-releases-vulkan-video-av1-decode-extension-vulkan-sdk-now-supports-h.264-h.265-encode),
|
||||
`VK_KHR_video_encode_av1` in 1.3.302
|
||||
(https://www.khronos.org/blog/khronos-announces-vulkan-video-encode-av1-encode-quantization-map-extensions);
|
||||
MoltenVK lists no video extension
|
||||
(https://raw.githubusercontent.com/KhronosGroup/MoltenVK/main/Docs/MoltenVK_Runtime_UserGuide.md).
|
||||
|
||||
## 4. GPU compute
|
||||
|
||||
- **WGSL integer semantics** (W3C WGSL, https://www.w3.org/TR/WGSL/): "i32 operations use
|
||||
two's complement arithmetic and have defined overflow behavior. Wrapping occurs."; `>>`
|
||||
on a signed value inserts copies of the sign bit (§8.10, Bit Expressions); integer
|
||||
division by zero evaluates to `e1` (§8.8); an out-of-bounds access is a dynamic error
|
||||
that the implementation "will not perform ... as written" (§6.5.7). So a WGSL kernel
|
||||
that keeps every value inside its type, and every index inside its buffer, has one
|
||||
result on every conformant device. The WAI kernels do: a block's cost is at most
|
||||
`block² · 765`, refused on the host where that passes 2^32 − 1.
|
||||
- **Metal** (MSL 4.1, https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf):
|
||||
unsigned integers wrap modulo 2^n and "The result of signed integer overflow is
|
||||
undefined"; atomics are relaxed, so only commutative integer reductions are order
|
||||
free. The WAI kernels use no atomics: each block's minimum is a tree reduction over a
|
||||
total order, so it does not depend on which thread visits which candidate.
|
||||
- **CUDA** (PTX, https://docs.nvidia.com/cuda/parallel-thread-execution/index.html): `sad`
|
||||
is `d = c + ((a<b) ? b-a : a-b)`; integer `add` exact; floating point may be fused.
|
||||
- **Unified memory**: on Apple silicon, `MTLStorageMode.shared` "is the default storage
|
||||
mode" (https://developer.apple.com/documentation/metal/mtlstoragemode/shared).
|
||||
- **BLAKE3 on GPUs**: the specification splits input into 1 KiB chunks hashed
|
||||
independently (https://github.com/BLAKE3-team/BLAKE3-specs/blob/master/blake3.pdf);
|
||||
published GPU ports were slower than one CPU core (a T4,
|
||||
https://github.com/chappjc/blake3-gpu-fork) or than 8 CPU threads (an integrated GPU,
|
||||
https://github.com/BLAKE3-team/BLAKE3/pull/80), and no energy comparison was found.
|
||||
Measured here in §9.
|
||||
|
||||
## 5. Neural engines
|
||||
|
||||
The Apple Neural Engine computes in FP16 ("The GPU and NE use float 16 precision",
|
||||
https://apple.github.io/coremltools/docs-guides/source/typed-execution.html); Core ML can
|
||||
permit it but not require it (`MLComputeUnits.cpuAndNeuralEngine`,
|
||||
https://developer.apple.com/documentation/coreml/mlcomputeunits/cpuandneuralengine);
|
||||
and "The ANE capabilities differ in each generation of hardware and software"
|
||||
(https://developer.apple.com/forums/thread/689135). FP16 represents integers exactly only
|
||||
up to 2048, and one 8-bit squared difference is 65025. **No WAI step can run on it.**
|
||||
|
||||
## 6. Determinism and conformance
|
||||
|
||||
### 6.1 AV1 decoding is exact, film grain aside
|
||||
|
||||
The AV1 specification, §7.2: "When film_grain_params_present is equal to 0, decoders
|
||||
shall produce output frames that are identical in all respects and have the same output
|
||||
order as those produced by the decoding process specified herein." With film grain a
|
||||
conformant decoder may instead match only the pre-grain frames of §7.18.2 and add grain
|
||||
with no "perceptually significant differences"
|
||||
(https://aomediacodec.github.io/av1-spec/av1-spec.pdf, Version 1.0.0 with Errata 1;
|
||||
source text https://raw.githubusercontent.com/AOMediaCodec/av1-spec/master/08.decoding.process.md).
|
||||
dav1d passes the Argon conformance streams with grain on
|
||||
(https://raw.githubusercontent.com/videolan/dav1d/master/README.md). Intel's Gen12 film
|
||||
grain "does not guarantee the bit match" (https://github.com/intel/media-driver/issues/1579);
|
||||
Intel and AMD have fixed AV1 decode bugs (https://github.com/intel/media-driver/issues/1108,
|
||||
https://www.phoronix.com/news/Mesa-25.0-rc3-Released). Apple publishes no conformance
|
||||
statement (unconfirmed).
|
||||
|
||||
**Tested [measured]**: the media engine decoded each big-buck-bunny rung, and its pictures
|
||||
were compared with dav1d's (§9). The ladder carries no film grain: dav1d gives the same
|
||||
frames with grain applied and with it exported.
|
||||
|
||||
### 6.2 Hardware encoders are not reproducible
|
||||
|
||||
No vendor states that its hardware encoder is deterministic; NVENC's guide documents
|
||||
split-frame encoding that turns on with the number of engines and the frame height
|
||||
(https://docs.nvidia.com/video-technologies/video-codec-sdk/13.0/nvenc-video-encoder-api-prog-guide/index.html),
|
||||
and Intel's low-power AV1 rate control runs in HuC firmware
|
||||
(https://raw.githubusercontent.com/intel/media-driver/master/README.md). Software
|
||||
encoders are reproducible only under pinned conditions: SVT-AV1 gives "the same" output
|
||||
for `--lp 1` and `--lp n` in the default CRF configuration
|
||||
(https://gitlab.com/AOMediaCodec/SVT-AV1/-/raw/master/Docs/CommonQuestions.md).
|
||||
|
||||
### 6.3 What it means for WAI
|
||||
|
||||
- **Receipts.** A package's bytes do not depend on where its steps ran, because every
|
||||
step is integer and the GPU kernels compute the same integers: the receipt, the index
|
||||
and every object are the CPU's (§9, `G360`). Where the steps ran is recorded only in a
|
||||
metered package's measurement claim, which §11.2 already exempts, so an unmetered
|
||||
package records nothing machine-specific.
|
||||
- **§11.2 on non-deterministic hardware.** No WAI step may be computed by a hardware
|
||||
encoder. A floor a packager is given is an input; a floor encoded on a hardware encoder
|
||||
is one §7.5 covers, its renderer naming the encoder, version, device and driver, and
|
||||
§11.2 does not hold its bytes.
|
||||
- **Verifiers' independence.** A check whose expected value the package states (an
|
||||
object's content hash) may run anywhere: a match shows the fabric computed it, and a
|
||||
mismatch is computed again on the CPU before it is reported. A floor states no
|
||||
decoded-output digest, so a hardware decode can never decide a floor verdict; and a
|
||||
hardware decoder may conceal an error that dav1d reports. The Python verifier stays on
|
||||
the CPU and shares no code with wai-rs; the GPU path is wai-pack's.
|
||||
|
||||
## 7. Measuring energy per fabric without root
|
||||
|
||||
### 7.1 macOS 27
|
||||
|
||||
- **IOReport** is private (`/usr/lib/libIOReport.dylib`; the SDK ships a `.tbd` stub and
|
||||
no header **[local]**). On macOS 27 its "Energy Model" CPU and ANE counters "can remain
|
||||
visible but stop updating" without a private entitlement, and `sudo` does not help
|
||||
(https://github.com/vladkens/macmon, "Power sources and macOS 27").
|
||||
**[measured]** on the build host: of 645 Energy Model channels, only `GPU Energy` (nJ)
|
||||
moved over a 6 s span during a VideoToolbox decode, a dav1d decode and idle; `CPU
|
||||
Energy`, every core, `ANE`, `AVE`, `MSR`, `DRAM`, `ISP` and `DISP` read zero. There is no
|
||||
decoder (AVD) energy channel at all. So **the media engine and the neural engine
|
||||
cannot be metered** on the fleet.
|
||||
- **CPU, per process**: `proc_pid_rusage(RUSAGE_INFO_V6)` gives `ri_energy_nj`, the
|
||||
kernel's account of the task's own CPU energy, readable for one's own process or child;
|
||||
`task_power_info_v2.task_energy` equals it, and its `gpu_energy.task_gpu_utilisation`
|
||||
reads 0 under a Metal load **[measured]**. Other load on the host does not enter it.
|
||||
- **GPU, per process**: the IORegistry's `IOAccelerator` user clients carry each
|
||||
creator's pid and an `accumulatedGPUTime` (ns), readable by any process **[measured]**.
|
||||
The repository's own measurement (`jouleclaw-energy`, `process_gpu.rs`) found that
|
||||
dividing the shared rail by GPU time charged a decode 3 to 15 times its own GPU energy,
|
||||
so the rail is attributed to a process only when it held at least 99% of every
|
||||
client's GPU time over the span. This change follows that rule, and reads the rail on
|
||||
its next republish at each end (it holds for hundreds of milliseconds, then catches up
|
||||
in one step).
|
||||
- `powermetrics` refuses to run without root ("must be invoked as the superuser")
|
||||
**[local]**; its own man page says its figures "are estimated and may be inaccurate"
|
||||
(https://keith.github.io/xcode-man-pages/powermetrics.1.html).
|
||||
|
||||
### 7.2 Linux and Windows (not run here)
|
||||
|
||||
- RAPL's `energy_uj` became root-only in v5.10 (commit `949dd0104c49`,
|
||||
https://github.com/torvalds/linux/commit/949dd0104c496fa7c14991a23c03c62e44637e71; Intel
|
||||
advisory INTEL-SA-00389); perf's RAPL events are system-wide only and Ubuntu sets
|
||||
`perf_event_paranoid` ≥ 2 (https://ubuntu.com/security/CVE-2023-2235). Reading CPU
|
||||
energy there takes an owner's act (`cap_perfmon`, or a group-readable `energy_uj`).
|
||||
- NVML's `nvmlDeviceGetTotalEnergyConsumption` (mJ, Volta and later) is not marked as
|
||||
needing root (https://docs.nvidia.com/deploy/nvml-api/latest/api/group__nvmlDeviceQueries.html);
|
||||
amdgpu's `power1_average` and Intel discrete GPUs' `energy1_input` are world-readable
|
||||
(https://raw.githubusercontent.com/torvalds/linux/master/drivers/gpu/drm/amd/pm/amdgpu_pm.c,
|
||||
https://raw.githubusercontent.com/torvalds/linux/master/drivers/gpu/drm/i915/i915_hwmon.c).
|
||||
- Windows' Energy Meter Interface is read unelevated by Firefox
|
||||
(https://raw.githubusercontent.com/mozilla-firefox/firefox/main/tools/profiler/core/PowerCounters-win.cpp).
|
||||
|
||||
## 8. The design choices, and what decided them
|
||||
|
||||
| Choice | Decided by |
|
||||
|---|---|
|
||||
| The GPU computes the motion search, SSE and BLAKE3 chunks, in WGSL integer kernels through wgpu (Metal, Vulkan, DX12), not in Metal only | WGSL's defined integer arithmetic (§4) gives one result on every conformant device and every fleet OS; the repository's GPU precedent (`wai-ops-gpu`) is wgpu |
|
||||
| A backend runs a step only after it gives the reference's output on qualifying inputs; any call it cannot take runs on the CPU | §11.2 must hold on every fabric; a device or driver can be wrong (§6.1's driver bugs) |
|
||||
| Placement by a measured cost table (µJ per call + nJ per unit), the CPU on a tie or where unmeasured | **[measured]** the GPU's fixed cost per call (1.7 mJ for a hash, 3.1 mJ for an SSE) exceeds what a pack's calls save, while the search saves 14× per unit (§9.3); placing by fabric alone made the floor-only pack dearer (§9.2) |
|
||||
| The fabric record lives in the measurement claim, not the receipt | the receipt and `LISTING` would otherwise differ by machine, voiding §11.2 for every package; the claim is already §11.2's exemption |
|
||||
| `fabrics` is absent rather than `null` | every existing claim, and every package that carries one, keeps its bytes |
|
||||
| Attributions `task` and `rail` only; no division of a shared rail by busy time | the repository's measurement of a 3–15× overcharge (§7.1) |
|
||||
| Hardware AV1 decode for sources and for informative floor decodes; never a floor verdict | AV1 §7.2 (exact without grain); floors state no digest; hardware may conceal errors |
|
||||
| VideoToolbox frames read from P010 exactly, never through FFmpeg's 16-to-8-bit conversion | **[measured]**: that conversion differs from dav1d's (§3.1) |
|
||||
| No hardware encoder computes a WAI step | §6.2 |
|
||||
| No neural-engine backend | §5 |
|
||||
|
||||
## 9. Measurements on big-buck-bunny
|
||||
|
||||
**Setup.** Big-buck-bunny, 596 s at 24 fps, 14,315 frames: phase 0's 256×144 proxy and
|
||||
the ladder's four 10-bit AV1 rungs (1080, 720, 540, 360). On the build host (M3 Ultra,
|
||||
macOS 27.0.1), shared with other work, the load averages given in each run's report.
|
||||
wai-pack at this branch, release, features `gpu,fabric-meter`; FFmpeg 9.0.2 with libdav1d
|
||||
1.5.4. Each run was metered by `xfab-meter`, which runs a command and reads:
|
||||
- the kernel's account of the child's CPU energy (`ri_energy_nj`) before reaping it, and
|
||||
of every VideoToolbox XPC service the run started (VideoToolbox decodes out of process:
|
||||
a 2,000-frame decode billed 1.20 J to FFmpeg and 1.09 J to its service);
|
||||
- the IOReport `GPU Energy` rail and every client's GPU time, every 200 ms, with idle
|
||||
brackets before and after.
|
||||
|
||||
The GPU figure is the rail where the run held at least 99% of all GPU time (`rail`);
|
||||
where it held less, there is no GPU figure, only the energy-measurement §3 marginal and
|
||||
its §4 verdict, never a division by GPU time. The media engine has no counter, so a
|
||||
media-engine run's figure is its CPU side alone, a lower bound.
|
||||
|
||||
The scripts and per-sample logs are kept on the build host in
|
||||
`wai-build/wai-packages/big-buck-bunny-xfab/` (`tools/`, `report-run2/`, `report/`). The
|
||||
CPU baselines are phase 0's and big-buck-bunny-v2's runs on the same host, measured the
|
||||
same way for the CPU.
|
||||
|
||||
### 9.1 Exact results
|
||||
|
||||
- **Packages are the same bytes on the GPU.** Phase 0's `av1-360` policy packed with
|
||||
`--fabric gpu`: pack digest `7454acc6…1118`, phase 0's, every file the same (`diff -r`),
|
||||
in three runs, and with `--fabric auto:<costs>` too. Big-buck-bunny-v2's run B (four
|
||||
floor rungs, the ladder's own CMAF, the base coded once): pack digest `3912bd7c…b9b7f`
|
||||
and receipt `e0256cd9…`, v2's. Its run C (floor-only): `f8871444…e032`, v2's.
|
||||
- **The media engine decodes what dav1d decodes.** Each of the four rungs, all 14,315
|
||||
frames: the SHA-256 of every frame in P010 equal between VideoToolbox's hardware frames
|
||||
and dav1d's output. And `wai-pack floor-decode` of the four-rung package: the same
|
||||
digest from both decoders for each of its 600 pieces, in the stream's own 10-bit form,
|
||||
the P010 words read back exactly.
|
||||
- **A source decoded by the media engine is the same source.** The proxy decoded from the
|
||||
1080p rung by VideoToolbox, then by the same scaler: SHA-256 `8d236f5b…b56d`, phase 0's
|
||||
`proxy.y4m` byte for byte, as dav1d's decode is.
|
||||
- **Placement.** In a 12-piece pack, 24,915 of 24,930 motion-search calls ran on the GPU
|
||||
before the map-wait fix. The other 15 were handed back to the CPU, with the same
|
||||
vectors.
|
||||
- **Instructions.** Phase 0's av1-360 pack retired 98.3 × 10¹² CPU instructions; the GPU
|
||||
pack retired 2.05 × 10¹².
|
||||
|
||||
### 9.2 Pack and verify, joules per fabric
|
||||
|
||||
Run 2, the GPU otherwise idle (its other clients under 1% of GPU time in every pack).
|
||||
|
||||
| Run | Wall | CPU (task) | GPU (rail) | Media | Total | Peak memory | CPU baseline |
|
||||
|---|---:|---:|---:|---:|---:|---:|---|
|
||||
| av1-360 pack, search on the GPU | 56 s | 573 J | 1,284 J | – | **1,857 J** | 0.95 GB | 20,299 J, 3,689 s, 3.04 GB (phase 0) |
|
||||
| av1-360 pack, `auto` (search on the GPU; SSE and hashes on the CPU) | 56 s | 571 J | 1,283 J | – | **1,854 J** | 0.76 GB | as above |
|
||||
| run B pack (4 floor rungs, CMAF floor), search on the GPU, `--jobs 12` | 71 s | 525 J | 1,239 J | – | **1,763 J** | 1.62 GB | 19,541 J, 980 s at `--jobs 6`, 0.17 GB (v2) |
|
||||
| run C pack (floor-only), hashes on the GPU | 2.2 s | 4.4 J | 2.3 J | – | **6.7 J** | 0.65 GB | 3.7 J, 1.5 s, 0.04 GB (v2) |
|
||||
| `wai-pack verify` av1-360, CPU | 32 s | 118 J | 0 | – | 118 J | 0.58 GB | 31–33 s (phase 0) |
|
||||
| `wai-pack verify` av1-360, hashes on the GPU | 31 s | 120 J | – (GPU shared) | – | ≥ 120 J | 2.04 GB | |
|
||||
| `wai-pack verify` run B, CPU | 34 s | 119 J | 0 | – | 119 J | 1.55 GB | 35 s, 121 J (v2) |
|
||||
| `wai-pack verify` run B, hashes on the GPU | 32 s | 122 J | 0.2 J | – | 122 J | 3.44 GB | |
|
||||
|
||||
What the table shows:
|
||||
- Moving the motion search to the GPU cuts a pack's energy about elevenfold. The CPU work
|
||||
left (residuals, rANS, digests, I/O: 116 s of CPU against phase 0's 3,635 s) shows that
|
||||
the search was at least 96.8% of phase 0's CPU time.
|
||||
- Moving the object hashes or SSE gains nothing: the floor-only pack, whose only movable
|
||||
step is hashing, costs more on the GPU.
|
||||
- `wai-pack verify` is dominated by the integer base's decode, which is sequential rANS,
|
||||
so it stays on the CPU.
|
||||
|
||||
### 9.3 The measured cost table (`wai-pack fabric calibrate`)
|
||||
|
||||
Each backend, two call sizes, 6 s each, fitted to `µJ per call + nJ per unit`:
|
||||
|
||||
| Step | CPU | GPU | Placement |
|
||||
|---|---|---|---|
|
||||
| `base-search` (unit: candidate-sample) | 0 + 20.2 nJ | 11,276 µJ + 1.47 nJ | GPU from about 600,000 units, a 32×24 frame at the policy's range; every BBB frame is 32 million |
|
||||
| `sse` (unit: byte) | 0 + 0.85 nJ | 3,136 µJ + 0.76 nJ | CPU below about 35 MB a call; a BBB piece is 5 MB |
|
||||
| `object-hash` (unit: byte) | 7 µJ + 1.33 nJ | 1,699 µJ + 1.43 nJ | CPU always: the GPU's BLAKE3 is no cheaper per byte either |
|
||||
|
||||
The GPU's per-call cost includes its idle and static draw over the call, since the rail is
|
||||
the whole GPU's.
|
||||
|
||||
### 9.4 Decoding: CPU against the media engine
|
||||
|
||||
Run 3 (VideoToolbox's XPC service counted; the GPU was shared in this run, which no
|
||||
decode used).
|
||||
|
||||
| Decode of the whole floor | dav1d: wall, CPU | VideoToolbox: wall, CPU (FFmpeg + its service), media engine |
|
||||
|---|---|---|
|
||||
| 1080p (`av1-1080`) | 23.1 s, 198.7 J | 22.0 s, 17.4 J, + unmetered |
|
||||
| 720p | 12.4 s, 111.0 J | 17.3 s, 11.3 J, + unmetered |
|
||||
| 540p | 7.7 s, 73.3 J | 14.1 s, 8.5 J, + unmetered |
|
||||
| 360p | 5.1 s, 53.8 J | 11.3 s, 15.9 J, + unmetered |
|
||||
| proxy source (1080p decoded and scaled to 256×144) | 22.7 s, 304.1 J | 25.6 s, 350.5 J, + unmetered |
|
||||
|
||||
What the table shows:
|
||||
- A floor decode on the media engine costs the CPU 8 to 11 times less at 540p to 1080p,
|
||||
and 3.4 times less at 360p, where the per-frame download dominates.
|
||||
- The engine's own draw is not visible to any unprivileged reader on macOS 27 (§7.1), so
|
||||
these are lower bounds.
|
||||
- For the proxy source, VideoToolbox saves nothing: the Lanczos scale and the P010 repack
|
||||
on the CPU cost more than the decode saved.
|
||||
- Placing `source-decode` on the media engine is therefore worth it only where the
|
||||
scaling also moves. Moving it would change the source's bytes, unless the scaler is
|
||||
exact.
|
||||
|
||||
### 9.5 A metered pack, sealed
|
||||
|
||||
- **Sealed.** The av1-360 pack with `--fabric gpu --meter fabric` was run when the GPU's
|
||||
other clients had held under 0.5% of its time over the preceding 10 s. It sealed a
|
||||
`ModelBased` figure of 2,248.4 J, whose measurement claim carries the fabric record:
|
||||
- the CPU's share is 475.4 J (`task`) and the GPU's 1,773.0 J (`rail`, the pack its sole
|
||||
user);
|
||||
- the steps are `base-search`, `sse` and `object-hash` on the GPU and every other step
|
||||
on the CPU, with no search call handed back to the CPU now that the map wait is fixed.
|
||||
- **Verified.** `wai-pack verify` accepts the package, and `pack_verify.py` holds every
|
||||
check of it (3622 of 3622), the claim among them.
|
||||
- **Refused.** The same metered pack of run B, a minute later, found the GPU shared (39.9 s
|
||||
of its 50.0 s) and was refused `pack-meter`, as was the calibration then.
|
||||
- The GPU figure here (1,773 J) and run 2's (1,284 J) differ by the GPU's state in each
|
||||
window. Both are indicative; the rule that the pack held the rail alone is what each
|
||||
states exactly.
|
||||
|
||||
### 9.6 Load, and what is indicative
|
||||
|
||||
- Times and energies are indicative. The host's one-minute load average was 14 to 50 over
|
||||
the runs in §9.2 and 25 to 90 in §9.4. Every run's load before and after is in its
|
||||
report.
|
||||
- The CPU figures are each process's own, by the kernel's attribution, so other load
|
||||
changes them only through contention.
|
||||
- The GPU figures in §9.2 are from runs that held the GPU alone. In run 3 another process
|
||||
held about a third of the GPU's time; the GPU packs then had no GPU figure, and the
|
||||
metered packs were refused `pack-meter` ("the GPU was shared: this process had 45.2 s
|
||||
of its 65.0 s, under 99%"), as §7.1's rule requires.
|
||||
- Instruction counts and digests are exact.
|
||||
|
||||
## 10. Judged unsound
|
||||
|
||||
- **Any WAI step on the neural engine** (§5).
|
||||
- **A hardware encoder for any WAI step**, and NVENC in particular for the floors here:
|
||||
floors are supplied, the packer refuses to encode one (`pack-floor-encode-unsupported`),
|
||||
and no hardware encoder's bytes are reproducible (§6.2). cbio-device's RTX 2060 has no
|
||||
AV1 encoder anyway; jetson-hub's GPU was not probed.
|
||||
- **VideoToolbox's motion estimation in place of §5.5's search** (§3.1).
|
||||
- **Dividing a shared GPU rail by GPU time** (§7.1).
|
||||
- **Media-engine and neural-engine energy on macOS 27**: no live unprivileged counter
|
||||
(§7.1); a step run there makes a metered span refuse rather than guess.
|
||||
- **A floor verdict from a hardware decoder** (§6.3).
|
||||
- **FFmpeg's P010-to-8-bit conversion in a comparison** (§3.1).
|
||||
|
|
@ -424,9 +424,11 @@ holds, for the session:
|
|||
room's media tracks it held, or 8 when it held none of them but other tracks of the room;
|
||||
- as a publisher: objects and bytes received, objects refused at the door, objects
|
||||
deduplicated, and the same per track;
|
||||
- `relay_energy`: `{"acquisition": "unmetered", "joules_micro": 0}`. No meter backs this
|
||||
relay. The 0 is the unmetered marker of WAI `extensions/energy-measurement.md` §2, never
|
||||
a measurement of zero joules, and must not be read or shown as one;
|
||||
- `relay_energy`: `{"acquisition": "unmetered", "joules_micro": 0}`. A session's own share
|
||||
of the relay's energy is not read: the counter covers the whole relay process, in which
|
||||
sessions run together (a room's record carries the relay's figure where it can, below).
|
||||
The 0 is the unmetered marker of WAI `extensions/energy-measurement.md` §2, never a
|
||||
measurement of zero joules, and must not be read or shown as one;
|
||||
- `publisher_signed`: where the door verified an object's receipt, the publisher's signed
|
||||
figure for producing it, summed under the acquisition class its leaf was signed with.
|
||||
These are the publisher's statements about the objects, not the relay's energy;
|
||||
|
|
@ -456,7 +458,33 @@ track of the room otherwise. The record holds:
|
|||
delivered to them;
|
||||
- the root of its ledger, one entry for each room grant its sessions ran under (or none, for
|
||||
sessions with no grant): the sessions, the bytes in and out and the subscriber time;
|
||||
- its energy, the unmetered marker.
|
||||
- its energy over the span (below).
|
||||
|
||||
**Its energy.** The relay reads a counter the host keeps, every 100 ms while it runs, and
|
||||
keeps four hours of samples (`src/energy.rs`):
|
||||
|
||||
- on macOS, the relay process's own CPU energy, `ri_energy_nj` from
|
||||
`proc_pid_rusage(getpid(), RUSAGE_INFO_V6)`, unprivileged: scope **task**, every thread of
|
||||
the relay and nothing else (not other processes, the GPU, the network interface, or work a
|
||||
system service does for the relay);
|
||||
- on Linux, the CPU packages' RAPL counters through powercap, scope **package**: the whole
|
||||
package, every process on it. It is read only with `[metering] dedicated_host = true`,
|
||||
the operator's statement that the host runs the relay alone, since it is otherwise not the
|
||||
relay's energy;
|
||||
- elsewhere, or with `[metering] energy = "off"`, none.
|
||||
|
||||
A record carries the counter's advance over its span as `OnChipCounter`, filtering state
|
||||
`Unknown`, with a declared uncertainty (the interpolation at each end of the span, the
|
||||
counter's lag at each end, for a package counter the noise a filtered counter's vendor
|
||||
states for the window, and the rounding to whole microjoules) over the span as its window,
|
||||
when three things hold: the counter was live across the span (WAI energy-measurement §3.1:
|
||||
it advanced in two consecutive intervals, in at least five, and in four of every five, over
|
||||
at least 300 ms); the relay served that room alone in the span (every session that
|
||||
overlapped it has ended and carried tracks of that room and of no other); and the figure is
|
||||
at least one microjoule. Otherwise the record carries the unmetered marker, and the relay
|
||||
logs why. A figure of the task scope covers the relay's CPU and nothing past it; a figure of
|
||||
the package scope covers the whole package of a host the operator stated was the relay's
|
||||
alone.
|
||||
|
||||
As a session closes, the relay ends its subscriptions one at a time, each when its request
|
||||
stream fails; the moments between are classed by what the session still holds, so a
|
||||
|
|
@ -634,8 +662,11 @@ SHA-256. `JOULE_MOQ_RELAY_LOG` sets the log level.
|
|||
- **Receipts:** the receipts track of a media track is found by the configured suffix, not
|
||||
by reading the catalog's `wai_receipts_track`; the relay signs no `delivery-gap` claim;
|
||||
datagrams at the door are checked against their group's receipts, not a property.
|
||||
- **Energy:** no meter backs the relay, so its own figure is always the unmetered marker.
|
||||
Attributing a metered host's energy to sessions is not done here.
|
||||
- **Energy:** a room's record carries the relay's own energy only where the relay served
|
||||
that room alone across the record's span; dividing the counter among rooms or sessions
|
||||
that ran together is not done here, and a session's summary always carries the unmetered
|
||||
marker. No figure is read on a host whose counter is neither the macOS task counter nor
|
||||
the RAPL package counter of a host stated to run the relay alone.
|
||||
- **The DVR store:** decisions withhold from FETCH and fills, not yet from live subscriptions
|
||||
(a reviewed item's `held` state, live-moderation §6.3, needs the review policy); objects
|
||||
are checked against their receipts at a restart, and by the door as they arrive where a door
|
||||
|
|
@ -658,6 +689,7 @@ All on loopback, every wait bounded:
|
|||
| `auth_tokens` | Tokens in SETUP, by value and by alias; UNAUTHORIZED; UNKNOWN_AUTH_TOKEN_ALIAS. |
|
||||
| `grant_proof` | A room grant with its proof over each transport gives its rights, each session with a challenge of its own; the grant as a bare token, a proof by another key, one for another relay and one over an earlier session's challenge each close the session with UNAUTHORIZED; a grant presented only in a request is refused it. |
|
||||
| `request_ids` | INVALID_REQUEST_ID for the wrong parity and for a repeat; requests in flight bounded by the stream limit. |
|
||||
| `relay_energy` | On a host with a counter (macOS's task counter; on Linux, the package counter with `dedicated_host`): a room's record, after a publisher sends for over a second, carries `OnChipCounter` with its evidence, reads back, and verifies; with a second room's session in the span it carries the unmetered marker; with `energy = "off"` it carries the marker. Where the host has no counter the test says SKIPPED, and with `JOULE_MOQ_RELAY_ENERGY_REQUIRE=1` it fails instead. The figure's arithmetic, its liveness rule and its uncertainty are `src/energy.rs`'s unit tests, which run on every host. |
|
||||
| `dvr` | A long publish crosses the window: the oldest groups go, whole, each media group with its receipts group, within the byte budget; a window by age empties as its groups pass it. FETCH inside the window (ascending and descending), across it (an End of Unknown Range through the last evicted group, then every object, from the store and the cache together) and before it (the marker alone); every object verifies against the fetched receipts. A restart keeps the store, drops and counts a flipped byte and bytes changed with their checks made again (only the receipt finds those), removes a cut-short write, and never serves what it dropped. A room key's redaction erases its target and withholds it, a deny withholds until an allow, a redacted event leaves the object that carries it, a decision by no moderator changes nothing, and the decisions hold across a restart. Without the seek power a FETCH and a fill below the live edge are refused `seek_not_granted`, and a FETCH at the live edge is served; with it, the window. The record of the room is `WMH1` version 2 with the store's figures. A room whose objects are sealed is never kept: its first sealed group is not written, its events and receipts already held and its decisions are deleted, nothing more of it is written, and so after a restart, while a room in the clear beside it keeps its window. A room in which a session proves a minor's publishing grant is never kept from then on, likewise, while a minor's grant that publishes nothing changes nothing. A window past 24 hours is refused. |
|
||||
| `age_serving` | A room rated 13 by its authority, the claim on the publisher's PUBLISH, and a stranger's rating ignored: a session at no threshold is refused the subscription `below_threshold`; the performer rates groups 5 to 7 of its track 18 on group 5's object, after group 4 went out, and from then the viewer at 16 is forwarded none of them while the viewer at 18 is forwarded all; a FETCH across the span is answered up to group 4 and one from inside it is refused; a SUBSCRIBE at a live edge rated 18 is refused. |
|
||||
| `age_room_service` | A room rated 18 by its authority, with the floor held (`[floor] room_keys` the clock): sessions proving the clock's key and the authority's (a `GRANT_PROOF` naming no grant) are served every group, verified; the clock's token with no proof, and a proof by a key that is no room key, are refused the subscription `below_threshold`; a proof naming the clock's key under another key's signature, and the clock's proof over another session's challenge, close the session `UNAUTHORIZED`. |
|
||||
|
|
|
|||
|
|
@ -61,6 +61,14 @@ hold_ms = 2000
|
|||
# key_path = "/etc/joule-moq-relay/summary-seed"
|
||||
# out_path = "/var/log/joule-moq-relay/sessions.jsonl"
|
||||
|
||||
# The relay's own energy in its per-hop session records (WAI live-metering §8): "auto"
|
||||
# reads the counter the host offers (macOS: the relay process's CPU energy; Linux: the CPU
|
||||
# packages' RAPL energy, only with dedicated_host = true, the statement that this host runs
|
||||
# the relay alone); "off" reads none, and every record carries the unmetered marker.
|
||||
[metering]
|
||||
energy = "auto"
|
||||
dedicated_host = false
|
||||
|
||||
# The room grants tokens carry, for the per-hop session records of WAI rooms (an example;
|
||||
# not a default): a session whose SETUP presents this token runs under this grant.
|
||||
[[metering.grants]]
|
||||
|
|
|
|||
|
|
@ -458,6 +458,25 @@ impl DvrConfig {
|
|||
pub struct MeteringConfig {
|
||||
#[serde(default)]
|
||||
pub grants: Vec<MeteredGrant>,
|
||||
/// Whether the relay reads its own energy ([`crate::energy`]): `"auto"`, the default,
|
||||
/// reads the counter the host offers; `"off"` reads none, and every record carries the
|
||||
/// unmetered marker.
|
||||
#[serde(default)]
|
||||
pub energy: EnergyMode,
|
||||
/// The operator's statement that this host runs the relay alone. Only then is a Linux
|
||||
/// host's CPU package counter, which covers every process on the package, read as the
|
||||
/// relay's energy.
|
||||
#[serde(default)]
|
||||
pub dedicated_host: bool,
|
||||
}
|
||||
|
||||
/// [`MeteringConfig::energy`].
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum EnergyMode {
|
||||
#[default]
|
||||
Auto,
|
||||
Off,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
|
|
|
|||
593
joule-code/joule-code-rs/crates/joule-moq-relay/src/energy.rs
Normal file
593
joule-code/joule-code-rs/crates/joule-moq-relay/src/energy.rs
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
//! The relay's own energy, read from a counter the host keeps (WAI `live-metering.md` §8,
|
||||
//! `energy-measurement.md` §2, §2.3, §3.1).
|
||||
//!
|
||||
//! A sampler thread reads one counter every [`INTERVAL`] for as long as the relay runs and
|
||||
//! keeps the samples of the last [`Meter::KEEP`]. A span's figure is the counter's advance
|
||||
//! across it, interpolated between the samples on either side of each end, and is a figure
|
||||
//! only where the counter was **live** across it (energy-measurement §3.1: it advanced in
|
||||
//! two consecutive intervals, in at least five, and in at least four of every five, over at
|
||||
//! least 300 ms). Otherwise there is no figure, and the relay signs the unmetered marker.
|
||||
//!
|
||||
//! The counters, and what each covers:
|
||||
//!
|
||||
//! - **macOS: the relay process's own CPU energy** (`ri_energy_nj` from
|
||||
//! `proc_pid_rusage(getpid(), RUSAGE_INFO_V6)`, public and unprivileged), scope
|
||||
//! [`Scope::Task`]: every thread of the relay process, as the kernel accounts it. Not
|
||||
//! other processes, not the GPU or the Neural Engine (the relay uses neither), not the
|
||||
//! network interface, and not work a system service does for the relay.
|
||||
//! - **Linux: the CPU packages' energy** (RAPL through powercap: every top-level zone whose
|
||||
//! name is `package-<n>`, each counter's wrap corrected at its `max_energy_range_uj`),
|
||||
//! scope [`Scope::Package`]: the whole package, every process on it included. It is read
|
||||
//! only where the operator states the host runs the relay alone (`dedicated_host`), since
|
||||
//! it is otherwise not the relay's energy.
|
||||
//! - Anywhere else, or where the counter cannot be read: none, and every figure is the
|
||||
//! unmetered marker.
|
||||
//!
|
||||
//! Both are on-chip counters, so a figure is `OnChipCounter`, with its filtering state
|
||||
//! `Unknown` (an unprivileged reader cannot see one) and a declared uncertainty
|
||||
//! ([`figure`]): the interpolation at each end, the counter's lag at each end (the relay's
|
||||
//! running threads' unfolded quanta for the task counter; the register's update period for
|
||||
//! the package counter), the noise a filtered package counter's vendor states for the
|
||||
//! window, and the rounding to whole microjoules.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
/// How often the sampler reads its counter.
|
||||
pub const INTERVAL: Duration = Duration::from_millis(100);
|
||||
/// The shortest span a figure covers (energy-measurement §3.1).
|
||||
pub const MIN_SPAN_US: u64 = 300_000;
|
||||
/// The task counter folds a running thread's energy at its context switch or its quantum,
|
||||
/// about 10 ms of the thread's CPU time: what a running thread can hold unfolded at a read.
|
||||
pub const QUANTUM_NS: u64 = 10_000_000;
|
||||
/// The package counter's register update period, about 1 ms.
|
||||
pub const REGISTER_PERIOD_NS: u64 = 1_000_000;
|
||||
/// The resolution floor of energy-measurement §2.3: `joules_micro × relative_ppm` at least.
|
||||
pub const RESOLUTION_FLOOR: u64 = 288_676;
|
||||
|
||||
/// What a counter covers.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Scope {
|
||||
/// The relay process's own CPU energy.
|
||||
Task,
|
||||
/// The CPU packages of a host that runs the relay alone.
|
||||
Package,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
Scope::Task => "task",
|
||||
Scope::Package => "package",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One read of a counter.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Reading {
|
||||
/// The counter's energy since an arbitrary origin, in nanojoules, never decreasing: a
|
||||
/// counter that wraps has its wrap corrected before it is a reading.
|
||||
pub nj: u64,
|
||||
/// The process's CPU time, user and system, in nanoseconds, where the counter has one.
|
||||
pub cpu_ns: Option<u64>,
|
||||
/// The threads of the process on a core at the read, where the counter has them.
|
||||
pub running: u32,
|
||||
}
|
||||
|
||||
/// A counter the sampler reads.
|
||||
pub trait Counter: Send + 'static {
|
||||
fn scope(&self) -> Scope;
|
||||
fn read(&mut self) -> Option<Reading>;
|
||||
}
|
||||
|
||||
/// One sample: a reading and when it was taken.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Sample {
|
||||
/// Microseconds since the Unix epoch.
|
||||
pub unix_us: u64,
|
||||
pub reading: Reading,
|
||||
}
|
||||
|
||||
/// A figure for a span: what [`figure`] gives where it can.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Figure {
|
||||
pub joules_micro: u64,
|
||||
/// The declared standard relative uncertainty, in parts per million, rounded up.
|
||||
pub relative_ppm: u32,
|
||||
/// The span, in microseconds.
|
||||
pub window_us: u64,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
impl Figure {
|
||||
/// energy-measurement §2.7's evidence after tag 4 (`OnChipCounter`): `relative_ppm` u32
|
||||
/// BE, `window_us` u64 BE, filtering `0` (`Unknown`).
|
||||
pub fn evidence(&self) -> Vec<u8> {
|
||||
let mut o = Vec::with_capacity(13);
|
||||
o.extend_from_slice(&self.relative_ppm.to_be_bytes());
|
||||
o.extend_from_slice(&self.window_us.to_be_bytes());
|
||||
o.push(0);
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
/// Why a span has no figure, so that its record carries the unmetered marker.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Unmetered {
|
||||
/// No counter backs the relay on this host, or the configuration turns it off.
|
||||
NoCounter,
|
||||
/// The span is shorter than 300 ms.
|
||||
SpanTooShort,
|
||||
/// The samples kept do not reach both ends of the span.
|
||||
NotSampled,
|
||||
/// The counter was not live across the span (energy-measurement §3.1).
|
||||
NotLive,
|
||||
/// The figure rounds to zero microjoules: no measurement of nothing (§4 there).
|
||||
Zero,
|
||||
/// The declared uncertainty does not fit its field.
|
||||
Uncertain,
|
||||
/// The relay did other work in the span (another room's sessions, a session still
|
||||
/// open), so its energy over the span is not this room's.
|
||||
Shared,
|
||||
}
|
||||
|
||||
impl Unmetered {
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
Unmetered::NoCounter => "no counter on this host",
|
||||
Unmetered::SpanTooShort => "span under 300 ms",
|
||||
Unmetered::NotSampled => "span outside the samples kept",
|
||||
Unmetered::NotLive => "counter not live across the span",
|
||||
Unmetered::Zero => "figure rounds to zero",
|
||||
Unmetered::Uncertain => "uncertainty does not fit",
|
||||
Unmetered::Shared => "the relay served other work in the span",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The counter's value at `t`, interpolated between the samples either side of it, in
|
||||
/// nanojoules, with the energy of the interval it falls in. `None` where no sample is at or
|
||||
/// before `t`, or none at or after it.
|
||||
fn at(samples: &[Sample], t: u64) -> Option<(u128, u128, usize)> {
|
||||
let after = samples.iter().position(|s| s.unix_us >= t)?;
|
||||
let s1 = samples[after];
|
||||
if s1.unix_us == t {
|
||||
// On a sample: the interval is the one before it, or after it at the first sample.
|
||||
let e = match (after.checked_sub(1).map(|i| samples[i]), samples.get(after + 1)) {
|
||||
(Some(s0), _) => u128::from(s1.reading.nj - s0.reading.nj),
|
||||
(None, Some(s2)) => u128::from(s2.reading.nj - s1.reading.nj),
|
||||
(None, None) => 0,
|
||||
};
|
||||
return Some((u128::from(s1.reading.nj), e, after));
|
||||
}
|
||||
let s0 = samples[after.checked_sub(1)?];
|
||||
let (dt, dx) = (u128::from(s1.unix_us - s0.unix_us), u128::from(s1.reading.nj - s0.reading.nj));
|
||||
let v = u128::from(s0.reading.nj) + dx * u128::from(t - s0.unix_us) / dt;
|
||||
Some((v, dx, after))
|
||||
}
|
||||
|
||||
/// The figure for the span `[from_us, to_us)`, from samples in time order.
|
||||
pub fn figure(samples: &[Sample], from_us: u64, to_us: u64, scope: Scope) -> Result<Figure, Unmetered> {
|
||||
if to_us <= from_us || to_us - from_us < MIN_SPAN_US {
|
||||
return Err(Unmetered::SpanTooShort);
|
||||
}
|
||||
let (e0, edge0, i0) = at(samples, from_us).ok_or(Unmetered::NotSampled)?;
|
||||
let (e1, edge1, i1) = at(samples, to_us).ok_or(Unmetered::NotSampled)?;
|
||||
// The bracket: from the sample at or before the span's start to the one at or after its
|
||||
// end. Liveness over its intervals (§3.1).
|
||||
let first = if samples[i0].unix_us == from_us { i0 } else { i0 - 1 };
|
||||
let bracket = &samples[first..=i1];
|
||||
let advanced: Vec<bool> = bracket.windows(2).map(|w| w[1].reading.nj > w[0].reading.nj).collect();
|
||||
let n = advanced.len();
|
||||
let live_count = advanced.iter().filter(|a| **a).count();
|
||||
let consecutive = advanced.windows(2).any(|w| w[0] && w[1]);
|
||||
if live_count < 5 || live_count * 5 < n * 4 || !consecutive {
|
||||
return Err(Unmetered::NotLive);
|
||||
}
|
||||
let nj = e1.saturating_sub(e0);
|
||||
let f = (nj + 500) / 1000;
|
||||
if f == 0 {
|
||||
return Err(Unmetered::Zero);
|
||||
}
|
||||
let f = u64::try_from(f).map_err(|_| Unmetered::Uncertain)?;
|
||||
// The uncertainty, in nanojoules squared.
|
||||
let sq = |x: f64| x * x;
|
||||
// Interpolation: within the interval an end falls in, the counter's course is unknown,
|
||||
// and the error is taken as rectangular over half that interval's energy either way.
|
||||
let mut var = (sq(edge0 as f64) + sq(edge1 as f64)) / 12.0;
|
||||
// The counter's lag at each end, rectangular over [0, L].
|
||||
let lag = |i: usize| -> f64 {
|
||||
match scope {
|
||||
Scope::Task => {
|
||||
// The energy a CPU-nanosecond costs, at its highest over the bracket's
|
||||
// intervals, times a quantum, for each thread on a core at the read.
|
||||
let per_ns = bracket
|
||||
.windows(2)
|
||||
.filter_map(|w| {
|
||||
let c = w[1].reading.cpu_ns?.checked_sub(w[0].reading.cpu_ns?)?;
|
||||
(c >= 1_000_000).then(|| (w[1].reading.nj - w[0].reading.nj) as f64 / c as f64)
|
||||
})
|
||||
.fold(nj as f64 / (to_us - from_us) as f64 / 1000.0, f64::max);
|
||||
let running = samples[i].reading.running.max(samples[i.saturating_sub(1)].reading.running).max(1);
|
||||
f64::from(running) * QUANTUM_NS as f64 * per_ns
|
||||
}
|
||||
Scope::Package => {
|
||||
// The register's update period at the bracket's highest interval power.
|
||||
let watts = bracket
|
||||
.windows(2)
|
||||
.map(|w| (w[1].reading.nj - w[0].reading.nj) as f64 / ((w[1].unix_us - w[0].unix_us).max(1) as f64 * 1000.0))
|
||||
.fold(0.0, f64::max);
|
||||
REGISTER_PERIOD_NS as f64 * watts
|
||||
}
|
||||
}
|
||||
};
|
||||
var += (sq(lag(i0)) + sq(lag(i1))) / 12.0;
|
||||
if scope == Scope::Package {
|
||||
// Two whole-microjoule register reads at each end (energy-measurement §2.3).
|
||||
var += 2.0 * sq(1000.0 / 6f64.sqrt());
|
||||
// A package counter may be filtered (§2.4): the noise INTEL-SA-00389's guidance
|
||||
// states, under 2 % at 1 s intervals and 5–15 % at 100 ms, taken at its top.
|
||||
let rel = if to_us - from_us >= 1_000_000 { 0.02 } else { 0.15 };
|
||||
var += sq(rel * nj as f64);
|
||||
}
|
||||
// The figure's own rounding to whole microjoules.
|
||||
var += sq(500.0 / 3f64.sqrt());
|
||||
let ppm = (var.sqrt() * 1e6 / (f as f64 * 1000.0)).ceil();
|
||||
let floor = RESOLUTION_FLOOR.div_ceil(f) as f64;
|
||||
let ppm = ppm.max(floor);
|
||||
if !ppm.is_finite() || ppm > f64::from(u32::MAX) || ppm < 1.0 {
|
||||
return Err(Unmetered::Uncertain);
|
||||
}
|
||||
Ok(Figure { joules_micro: f, relative_ppm: ppm as u32, window_us: to_us - from_us, scope })
|
||||
}
|
||||
|
||||
/// The sampler: a thread reading one counter, and the samples it keeps.
|
||||
pub struct Meter {
|
||||
scope: Scope,
|
||||
samples: Arc<Mutex<VecDeque<Sample>>>,
|
||||
stop: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Meter {
|
||||
/// How long the samples reach back: a record signed later than this after its span
|
||||
/// began carries the unmetered marker.
|
||||
pub const KEEP: Duration = Duration::from_secs(4 * 3600);
|
||||
|
||||
/// Start sampling `counter` every `interval`.
|
||||
pub fn start(mut counter: Box<dyn Counter>, interval: Duration) -> Meter {
|
||||
let scope = counter.scope();
|
||||
let samples = Arc::new(Mutex::new(VecDeque::new()));
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let keep = (Self::KEEP.as_micros() / interval.as_micros().max(1)) as usize + 1;
|
||||
let (s, halt) = (samples.clone(), stop.clone());
|
||||
std::thread::Builder::new()
|
||||
.name("relay-energy".into())
|
||||
.spawn(move || {
|
||||
while !halt.load(Ordering::Relaxed) {
|
||||
if let Some(reading) = counter.read() {
|
||||
let mut q = s.lock().unwrap();
|
||||
if q.len() >= keep {
|
||||
q.pop_front();
|
||||
}
|
||||
q.push_back(Sample { unix_us: now_unix_us(), reading });
|
||||
}
|
||||
std::thread::sleep(interval);
|
||||
}
|
||||
})
|
||||
.expect("the energy sampler's thread starts");
|
||||
Meter { scope, samples, stop }
|
||||
}
|
||||
|
||||
/// The counter this host offers the relay: macOS's task counter, or, where `dedicated`
|
||||
/// and readable, Linux's package counter. `None` elsewhere.
|
||||
pub fn host(dedicated: bool) -> Option<Box<dyn Counter>> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let _ = dedicated;
|
||||
macos::TaskCounter::open().map(|c| Box::new(c) as Box<dyn Counter>)
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if !dedicated {
|
||||
return None;
|
||||
}
|
||||
powercap::PackageCounter::open_in(std::path::Path::new("/sys/class/powercap")).map(|c| Box::new(c) as Box<dyn Counter>)
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
let _ = dedicated;
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scope(&self) -> Scope {
|
||||
self.scope
|
||||
}
|
||||
|
||||
/// The figure for `[from_us, to_us)`, Unix microseconds.
|
||||
pub fn figure(&self, from_us: u64, to_us: u64) -> Result<Figure, Unmetered> {
|
||||
let q = self.samples.lock().unwrap();
|
||||
let v: Vec<Sample> = q.iter().copied().collect();
|
||||
figure(&v, from_us, to_us, self.scope)
|
||||
}
|
||||
|
||||
/// The most recent sample's time, Unix microseconds, once there is one.
|
||||
pub fn last_sample_us(&self) -> Option<u64> {
|
||||
self.samples.lock().unwrap().back().map(|s| s.unix_us)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Meter {
|
||||
fn drop(&mut self) {
|
||||
self.stop.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
fn now_unix_us() -> u64 {
|
||||
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_micros() as u64).unwrap_or(0)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[allow(unsafe_code)]
|
||||
mod macos {
|
||||
use super::{Counter, Reading, Scope};
|
||||
use std::os::raw::{c_int, c_void};
|
||||
|
||||
/// `RUSAGE_INFO_V6`, its size, and the offsets read: `ri_user_time`, `ri_system_time`
|
||||
/// (Mach absolute time units) and `ri_energy_nj`, from `<sys/resource.h>` (the struct is
|
||||
/// a 16-byte UUID then 56 `uint64_t`s; the same layout `jcp-energy` reads).
|
||||
const FLAVOR: c_int = 6;
|
||||
const SIZE: usize = 464;
|
||||
const USER_TIME: usize = 16;
|
||||
const SYSTEM_TIME: usize = 24;
|
||||
const ENERGY_NJ: usize = 336;
|
||||
|
||||
unsafe extern "C" {
|
||||
fn proc_pid_rusage(pid: c_int, flavor: c_int, buffer: *mut c_void) -> c_int;
|
||||
fn proc_pidinfo(pid: c_int, flavor: c_int, arg: u64, buffer: *mut c_void, buffersize: c_int) -> c_int;
|
||||
fn mach_timebase_info(info: *mut [u32; 2]) -> c_int;
|
||||
fn getpid() -> c_int;
|
||||
}
|
||||
|
||||
#[repr(C, align(8))]
|
||||
struct Info([u8; SIZE]);
|
||||
|
||||
fn word(b: &[u8; SIZE], at: usize) -> u64 {
|
||||
u64::from_ne_bytes(b[at..at + 8].try_into().expect("8 bytes"))
|
||||
}
|
||||
|
||||
/// The relay process's own CPU energy counter.
|
||||
pub struct TaskCounter {
|
||||
/// Mach absolute time units to nanoseconds: numer / denom.
|
||||
timebase: (u64, u64),
|
||||
}
|
||||
|
||||
impl TaskCounter {
|
||||
pub fn open() -> Option<TaskCounter> {
|
||||
let mut tb = [0u32; 2];
|
||||
if unsafe { mach_timebase_info(&mut tb) } != 0 || tb[1] == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut c = TaskCounter { timebase: (u64::from(tb[0]), u64::from(tb[1])) };
|
||||
c.read()?;
|
||||
Some(c)
|
||||
}
|
||||
|
||||
/// `pti_numrunning` of `struct proc_taskinfo` (`PROC_PIDTASKINFO`, 4): 96 bytes, the
|
||||
/// running-thread count at byte 88 after eleven 64-bit and integer fields.
|
||||
fn running() -> u32 {
|
||||
let mut b = [0u8; 96];
|
||||
let n = unsafe { proc_pidinfo(getpid(), 4, 0, b.as_mut_ptr().cast(), 96) };
|
||||
if n != 96 {
|
||||
return 1;
|
||||
}
|
||||
i32::from_ne_bytes(b[88..92].try_into().expect("4 bytes")).max(1) as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl Counter for TaskCounter {
|
||||
fn scope(&self) -> Scope {
|
||||
Scope::Task
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Option<Reading> {
|
||||
let mut info = Info([0; SIZE]);
|
||||
if unsafe { proc_pid_rusage(getpid(), FLAVOR, info.0.as_mut_ptr().cast()) } != 0 {
|
||||
return None;
|
||||
}
|
||||
let mach = word(&info.0, USER_TIME).saturating_add(word(&info.0, SYSTEM_TIME));
|
||||
let cpu_ns = (u128::from(mach) * u128::from(self.timebase.0) / u128::from(self.timebase.1)) as u64;
|
||||
Some(Reading { nj: word(&info.0, ENERGY_NJ), cpu_ns: Some(cpu_ns), running: Self::running() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The CPU packages' RAPL counters through powercap: built everywhere, so its tests run on
|
||||
/// a tree made for them; read on Linux alone.
|
||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
mod powercap {
|
||||
use super::{Counter, Reading, Scope};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// The CPU packages' RAPL counters through powercap.
|
||||
pub struct PackageCounter {
|
||||
/// Per package: its `energy_uj`, its `max_energy_range_uj`, and its last raw value.
|
||||
zones: Vec<(PathBuf, u64, u64)>,
|
||||
/// The sum of every package's wrap-corrected advance since the counter opened, in µJ.
|
||||
total_uj: u64,
|
||||
}
|
||||
|
||||
fn read_u64(p: &std::path::Path) -> Option<u64> {
|
||||
std::fs::read_to_string(p).ok()?.trim().parse().ok()
|
||||
}
|
||||
|
||||
impl PackageCounter {
|
||||
/// Every top-level `intel-rapl:<n>` zone whose `name` is `package-<n>`, each readable
|
||||
/// with a known range; none if any package cannot be read, so the figure never
|
||||
/// leaves a package out.
|
||||
pub fn open_in(root: &Path) -> Option<PackageCounter> {
|
||||
let mut zones = Vec::new();
|
||||
for e in std::fs::read_dir(root).ok()?.flatten() {
|
||||
let name = e.file_name().to_string_lossy().into_owned();
|
||||
let Some(rest) = name.strip_prefix("intel-rapl:") else { continue };
|
||||
if rest.is_empty() || !rest.bytes().all(|b| b.is_ascii_digit()) {
|
||||
continue;
|
||||
}
|
||||
let zone = std::fs::read_to_string(e.path().join("name")).ok()?;
|
||||
let zone = zone.trim();
|
||||
let Some(n) = zone.strip_prefix("package-") else { continue };
|
||||
if n.is_empty() || !n.bytes().all(|b| b.is_ascii_digit()) {
|
||||
continue;
|
||||
}
|
||||
let energy = e.path().join("energy_uj");
|
||||
let range = read_u64(&e.path().join("max_energy_range_uj")).filter(|r| *r > 0)?;
|
||||
let now = read_u64(&energy)?;
|
||||
zones.push((energy, range, now));
|
||||
}
|
||||
if zones.is_empty() {
|
||||
return None;
|
||||
}
|
||||
zones.sort();
|
||||
Some(PackageCounter { zones, total_uj: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl Counter for PackageCounter {
|
||||
fn scope(&self) -> Scope {
|
||||
Scope::Package
|
||||
}
|
||||
|
||||
fn read(&mut self) -> Option<Reading> {
|
||||
let mut add = 0u64;
|
||||
let mut now = Vec::with_capacity(self.zones.len());
|
||||
for (p, range, last) in &self.zones {
|
||||
let v = read_u64(p)?;
|
||||
if v > *range {
|
||||
return None;
|
||||
}
|
||||
add += if v >= *last { v - last } else { range - last + v };
|
||||
now.push(v);
|
||||
}
|
||||
for (z, v) in self.zones.iter_mut().zip(now) {
|
||||
z.2 = v;
|
||||
}
|
||||
self.total_uj += add;
|
||||
Some(Reading { nj: self.total_uj.saturating_mul(1000), cpu_ns: None, running: 1 })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn zone(root: &Path, dir: &str, name: &str, energy: u64, range: u64) {
|
||||
let d = root.join(dir);
|
||||
std::fs::create_dir_all(&d).unwrap();
|
||||
std::fs::write(d.join("name"), format!("{name}\n")).unwrap();
|
||||
std::fs::write(d.join("energy_uj"), format!("{energy}\n")).unwrap();
|
||||
std::fs::write(d.join("max_energy_range_uj"), format!("{range}\n")).unwrap();
|
||||
}
|
||||
|
||||
/// Every package zone and nothing else is summed, each wrap corrected at its own
|
||||
/// range; a tree with no package, or a package that cannot be read, opens nothing.
|
||||
#[test]
|
||||
fn packages_alone_each_wrap_corrected() {
|
||||
let root = std::env::temp_dir().join(format!("relay-powercap-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
zone(&root, "intel-rapl:0", "package-0", 1_000, 10_000);
|
||||
zone(&root, "intel-rapl:0:0", "core", 5, 10_000);
|
||||
zone(&root, "intel-rapl:1", "psys", 70, 10_000);
|
||||
zone(&root, "intel-rapl:2", "package-1", 9_990, 10_000);
|
||||
let mut c = PackageCounter::open_in(&root).expect("two packages");
|
||||
assert_eq!(c.zones.len(), 2);
|
||||
assert_eq!(c.read().unwrap().nj, 0);
|
||||
std::fs::write(root.join("intel-rapl:0/energy_uj"), "1500\n").unwrap();
|
||||
std::fs::write(root.join("intel-rapl:2/energy_uj"), "20\n").unwrap(); // wrapped: 10 + 20
|
||||
std::fs::write(root.join("intel-rapl:1/energy_uj"), "99999\n").unwrap(); // psys: never read
|
||||
assert_eq!(c.read().unwrap().nj, (500 + 30) * 1000);
|
||||
std::fs::write(root.join("intel-rapl:0/energy_uj"), "20000\n").unwrap(); // past its range
|
||||
assert_eq!(c.read(), None, "a value past its range is no reading");
|
||||
std::fs::remove_file(root.join("intel-rapl:2/energy_uj")).unwrap();
|
||||
assert!(PackageCounter::open_in(&root).is_none(), "a package that cannot be read: nothing opens");
|
||||
let empty = root.join("empty");
|
||||
std::fs::create_dir_all(empty.join("intel-rapl:1")).unwrap();
|
||||
std::fs::write(empty.join("intel-rapl:1/name"), "psys\n").unwrap();
|
||||
assert!(PackageCounter::open_in(&empty).is_none(), "no package zone");
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Samples every 100 ms from `t0`, the counter advancing by `step(i)` nJ before sample
|
||||
/// `i`, with `cpu` ns of CPU per interval and `running` threads.
|
||||
fn series(t0: u64, n: usize, step: impl Fn(usize) -> u64, cpu: u64, running: u32) -> Vec<Sample> {
|
||||
let (mut nj, mut c) = (0u64, 0u64);
|
||||
(0..n)
|
||||
.map(|i| {
|
||||
if i > 0 {
|
||||
nj += step(i);
|
||||
c += cpu;
|
||||
}
|
||||
Sample { unix_us: t0 + 100_000 * i as u64, reading: Reading { nj, cpu_ns: Some(c), running } }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_steady_counter_gives_its_advance_with_an_uncertainty_over_the_floor() {
|
||||
// 50 mJ an interval: 0.5 W over 2 s.
|
||||
let s = series(1_000_000, 30, |_| 50_000_000, 20_000_000, 2);
|
||||
let f = figure(&s, 1_000_000 + 250_000, 1_000_000 + 2_250_000, Scope::Task).unwrap();
|
||||
assert_eq!(f.joules_micro, 1_000_000, "the interpolated advance over 2 s");
|
||||
assert_eq!(f.window_us, 2_000_000);
|
||||
assert!(f.joules_micro * u64::from(f.relative_ppm) >= RESOLUTION_FLOOR);
|
||||
// Each end's interval energy (50 mJ) and two threads' quantum at 2.5 W/CPU-s each.
|
||||
let want = ((2.0 * 5e7f64.powi(2) + 2.0 * (2.0 * 1e7 * 2.5f64).powi(2)) / 12.0 + (500.0f64 / 3f64.sqrt()).powi(2)).sqrt() * 1e6 / 1e9;
|
||||
assert!((f64::from(f.relative_ppm) - want.ceil()).abs() <= 1.0, "{} against {}", f.relative_ppm, want.ceil());
|
||||
assert_eq!(f.evidence().len(), 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_frozen_or_sparse_counter_is_not_live() {
|
||||
let frozen = series(0, 30, |_| 0, 0, 1);
|
||||
assert_eq!(figure(&frozen, 100_000, 2_100_000, Scope::Task), Err(Unmetered::NotLive));
|
||||
// A counter refreshed every fifth interval: one advance in five.
|
||||
let lumpy = series(0, 30, |i| if i % 5 == 0 { 250_000_000 } else { 0 }, 0, 1);
|
||||
assert_eq!(figure(&lumpy, 100_000, 2_100_000, Scope::Task), Err(Unmetered::NotLive));
|
||||
// Every other interval: two in four, under four in five.
|
||||
let half = series(0, 30, |i| if i % 2 == 0 { 100_000_000 } else { 0 }, 0, 1);
|
||||
assert_eq!(figure(&half, 100_000, 2_100_000, Scope::Task), Err(Unmetered::NotLive));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_span_the_samples_do_not_hold_or_too_short_has_no_figure() {
|
||||
// 1 µJ (1 000 nJ) an interval.
|
||||
let s = series(1_000_000, 10, |_| 1_000, 1_000_000, 1);
|
||||
assert_eq!(figure(&s, 500_000, 1_600_000, Scope::Task), Err(Unmetered::NotSampled));
|
||||
assert_eq!(figure(&s, 1_100_000, 2_000_000, Scope::Task), Err(Unmetered::NotSampled));
|
||||
assert_eq!(figure(&s, 1_100_000, 1_300_000, Scope::Task), Err(Unmetered::SpanTooShort));
|
||||
// 1 µJ an interval over 0.8 s: 8 µJ, live, and sealed with a declaration at the floor.
|
||||
let f = figure(&s, 1_050_000, 1_850_000, Scope::Task).unwrap();
|
||||
assert_eq!(f.joules_micro, 8);
|
||||
assert!(8 * u64::from(f.relative_ppm) >= RESOLUTION_FLOOR);
|
||||
// Less than half a microjoule in all: no figure, never a figure of zero.
|
||||
let tiny = series(0, 12, |_| 40, 0, 1);
|
||||
assert_eq!(figure(&tiny, 0, 1_000_000, Scope::Task), Err(Unmetered::Zero));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_package_figure_declares_the_filtering_noise_for_its_window() {
|
||||
let s = series(0, 40, |_| 2_500_000_000, 0, 1); // 25 W
|
||||
let long = figure(&s, 0, 2_000_000, Scope::Package).unwrap();
|
||||
assert!(long.relative_ppm >= 20_000, "{}", long.relative_ppm);
|
||||
let short = figure(&s, 0, 500_000, Scope::Package).unwrap();
|
||||
assert!(short.relative_ppm >= 150_000, "{}", short.relative_ppm);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,13 +18,19 @@
|
|||
//! - **Receipts at the door** ([`door`]): optional per namespace, with `joule-moq`'s
|
||||
//! verifier.
|
||||
//! - **Session summaries** ([`summary`]): bytes and objects per subscriber, signed by the
|
||||
//! relay at session end; its own energy is reported as unmetered, since no meter backs
|
||||
//! it.
|
||||
//! relay at session end; a session's own energy is reported as unmetered, since sessions
|
||||
//! run together in one process and a counter cannot tell their shares apart.
|
||||
//! - **Its own energy** ([`energy`]): a counter the host keeps, sampled while the relay
|
||||
//! runs: macOS's per-task CPU energy of the relay process, or, on a Linux host the
|
||||
//! operator states runs the relay alone, the CPU packages' RAPL energy; `OnChipCounter`
|
||||
//! with its scope and declared uncertainty, or the unmetered marker where it is not live
|
||||
//! or not the room's.
|
||||
//! - **Per-hop session records** ([`metering`]): WAI's `WMH1` record of a room
|
||||
//! (`live-metering.md` §3), signed by the relay from its summaries: per track the
|
||||
//! objects and bytes in and out and the drops by reason, its subscribers' time by layer
|
||||
//! class, the events it delivered, and a ledger of the room grants its sessions ran under.
|
||||
//! With a DVR store it signs version 2, with the store's figures (§3.9).
|
||||
//! class, the events it delivered, a ledger of the room grants its sessions ran under, and
|
||||
//! its own energy over the span where it served that room alone. With a DVR store it
|
||||
//! signs version 2, with the store's figures (§3.9).
|
||||
//! - **DVR** ([`dvr`]): a window of the tracks under each configured prefix, with their
|
||||
//! receipts, on disk, served to FETCH below the live edge (WAI `live-recording.md` §8):
|
||||
//! evicted oldest first by whole group, kept across a restart with every object checked
|
||||
|
|
@ -45,6 +51,7 @@ pub mod client;
|
|||
pub mod config;
|
||||
pub mod door;
|
||||
pub mod dvr;
|
||||
pub mod energy;
|
||||
pub mod events;
|
||||
pub mod floor;
|
||||
pub mod metering;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,12 @@
|
|||
//! the room id), the objects and bytes in and out, the objects dropped by reason with those
|
||||
//! whose receipts it forwarded; its subscriber sessions' time by layer class, from the
|
||||
//! subscriptions each held; the events it delivered; and a ledger, one entry for each room
|
||||
//! grant its sessions ran under, by the token each presented in SETUP. Its own energy is the
|
||||
//! unmetered marker, since no meter backs this relay. It connects to no other hop, so every
|
||||
//! session of it that subscribes is a subscriber session (§3.3 there).
|
||||
//! grant its sessions ran under, by the token each presented in SETUP. Its own energy is read
|
||||
//! from the counter the host keeps ([`crate::energy`]): `OnChipCounter` with its declared
|
||||
//! uncertainty, where the counter was live across the record's span and the relay served
|
||||
//! that room alone in it; the unmetered marker otherwise, never a figure of zero. It
|
||||
//! connects to no other hop, so every session of it that subscribes is a subscriber session
|
||||
//! (§3.3 there).
|
||||
//!
|
||||
//! A relay that keeps a DVR store signs version 2 of the record, which adds the store's
|
||||
//! figures for the room over the span (§3.9): the objects and bytes it served from the
|
||||
|
|
@ -200,7 +203,11 @@ pub struct HopRecord {
|
|||
pub end: u64,
|
||||
/// 0 with class tag 0: the unmetered marker.
|
||||
pub joules: u64,
|
||||
/// energy-measurement §2.7's tag: 0 (the unmetered marker) or 4 (`OnChipCounter`).
|
||||
pub class_tag: u8,
|
||||
/// The tag's evidence as §2.7 writes it: none for tag 0; for tag 4, `relative_ppm` u32
|
||||
/// BE, `window_us` u64 BE and the filtering byte.
|
||||
pub class_evidence: Vec<u8>,
|
||||
pub entries: Vec<Entry>,
|
||||
pub sessions: u32,
|
||||
pub subscriber_us: [u64; CLASSES],
|
||||
|
|
@ -224,6 +231,7 @@ impl HopRecord {
|
|||
o.extend_from_slice(&self.end.to_le_bytes());
|
||||
o.extend_from_slice(&self.joules.to_le_bytes());
|
||||
o.push(self.class_tag);
|
||||
o.extend_from_slice(&self.class_evidence);
|
||||
o.extend_from_slice(&(self.entries.len() as u16).to_le_bytes());
|
||||
for e in &self.entries {
|
||||
o.push(e.kind);
|
||||
|
|
@ -276,6 +284,15 @@ impl HopRecord {
|
|||
self
|
||||
}
|
||||
|
||||
/// The record with the relay's own energy over its span, `OnChipCounter` with its
|
||||
/// evidence, signed again with `key`.
|
||||
pub fn with_energy(mut self, f: &crate::energy::Figure, key: &SigningKey) -> HopRecord {
|
||||
self.joules = f.joules_micro;
|
||||
self.class_tag = 4;
|
||||
self.class_evidence = f.evidence();
|
||||
self.sign(key)
|
||||
}
|
||||
|
||||
/// Check the signature under the key the record names.
|
||||
pub fn verify(&self) -> Result<(), String> {
|
||||
let key = VerifyingKey::from_bytes(&self.hop).map_err(|e| e.to_string())?;
|
||||
|
|
@ -284,7 +301,8 @@ impl HopRecord {
|
|||
key.verify(&m, &Signature::from_bytes(&self.sig)).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Read a record this module wrote: an unmetered one, as the relay signs.
|
||||
/// Read a record this module wrote: unmetered, or `OnChipCounter` with its evidence
|
||||
/// (live-metering §3.6's rules on the class), as the relay signs.
|
||||
pub fn decode(b: &[u8], ledger: &[u8]) -> Result<HopRecord, String> {
|
||||
let mut p = 0usize;
|
||||
let mut take = |n: usize| -> Result<&[u8], String> {
|
||||
|
|
@ -305,9 +323,20 @@ impl HopRecord {
|
|||
let role = take(1)?[0];
|
||||
let (start, end, joules) = (u64_of(take(8)?), u64_of(take(8)?), u64_of(take(8)?));
|
||||
let class_tag = take(1)?[0];
|
||||
if class_tag != 0 {
|
||||
return Err("this reader reads the unmetered marker alone".into());
|
||||
}
|
||||
let class_evidence = match class_tag {
|
||||
0 if joules == 0 => Vec::new(),
|
||||
4 if joules > 0 => {
|
||||
let e = take(13)?.to_vec();
|
||||
let ppm = u64::from(u32::from_be_bytes(e[..4].try_into().expect("4")));
|
||||
let window = u64::from_be_bytes(e[4..12].try_into().expect("8"));
|
||||
if ppm == 0 || window == 0 || e[12] > 2 || u128::from(joules) * u128::from(ppm) < u128::from(crate::energy::RESOLUTION_FLOOR) {
|
||||
return Err("an OnChipCounter class whose evidence breaks live-metering §3.6".into());
|
||||
}
|
||||
e
|
||||
}
|
||||
0 | 4 => return Err("the tag is 0 exactly when the figure is 0".into()),
|
||||
_ => return Err("this reader reads the unmetered marker or OnChipCounter, as this relay signs".into()),
|
||||
};
|
||||
let n = u16::from_le_bytes(take(2)?.try_into().expect("2")) as usize;
|
||||
let mut entries = Vec::with_capacity(n);
|
||||
for _ in 0..n {
|
||||
|
|
@ -363,7 +392,7 @@ impl HopRecord {
|
|||
}
|
||||
})
|
||||
.collect();
|
||||
let r = HopRecord { room, hop, role, start, end, joules, class_tag, entries, sessions, subscriber_us, events, ledger, dvr, sig };
|
||||
let r = HopRecord { room, hop, role, start, end, joules, class_tag, class_evidence, entries, sessions, subscriber_us, events, ledger, dvr, sig };
|
||||
if r.ledger_root() != ledger_root {
|
||||
return Err("the ledger is not the one the record signs".into());
|
||||
}
|
||||
|
|
@ -471,6 +500,7 @@ pub fn hop_record(summaries: &[SessionSummary], room: [u8; 32], t0_unix_us: u64,
|
|||
end,
|
||||
joules: 0,
|
||||
class_tag: 0,
|
||||
class_evidence: Vec::new(),
|
||||
entries: entries.into_values().collect(),
|
||||
sessions,
|
||||
subscriber_us,
|
||||
|
|
@ -501,6 +531,31 @@ mod tests {
|
|||
assert_eq!(layer_class([format!("{room}-{owner}-events--0")].iter().map(String::as_str)), NO_MEDIA);
|
||||
}
|
||||
|
||||
/// A record with the relay's measured energy carries tag 4 and its evidence, reads
|
||||
/// back, and verifies; the reader refuses what live-metering §3.6 refuses of the class.
|
||||
#[test]
|
||||
fn a_measured_record_reads_back_and_a_bad_class_does_not() {
|
||||
let key = SigningKey::from_bytes(&[3; 32]);
|
||||
let rec = hop_record(&[], [9; 32], 0, &key, None);
|
||||
assert_eq!((rec.joules, rec.class_tag, rec.class_evidence.len()), (0, 0, 0));
|
||||
let f = crate::energy::Figure { joules_micro: 52_000, relative_ppm: 41_000, window_us: 2_000_000, scope: crate::energy::Scope::Task };
|
||||
let m = rec.clone().with_energy(&f, &key);
|
||||
m.verify().unwrap();
|
||||
assert_eq!(HopRecord::decode(&m.to_bytes(), &m.ledger_bytes()).unwrap(), m);
|
||||
assert_eq!(m.to_bytes().len(), rec.to_bytes().len() + 13);
|
||||
let bad = |edit: &dyn Fn(&mut HopRecord)| {
|
||||
let mut r = m.clone();
|
||||
edit(&mut r);
|
||||
HopRecord::decode(&r.sign(&key).to_bytes(), &m.ledger_bytes())
|
||||
};
|
||||
assert!(bad(&|r| r.class_evidence[..4].copy_from_slice(&5u32.to_be_bytes())).is_err(), "below the resolution floor");
|
||||
assert!(bad(&|r| r.class_evidence[4..12].copy_from_slice(&0u64.to_be_bytes())).is_err(), "no window");
|
||||
assert!(bad(&|r| r.class_evidence[12] = 3).is_err(), "a filtering byte past 2");
|
||||
assert!(bad(&|r| r.joules = 0).is_err(), "a class beside zero");
|
||||
assert!(bad(&|r| { r.class_tag = 1; r.class_evidence.clear(); }).is_err(), "HwShunt");
|
||||
assert!(bad(&|r| { r.class_tag = 0; r.class_evidence.clear(); }).is_err(), "a figure with no class");
|
||||
}
|
||||
|
||||
/// The root is RFC 6962's: two leaves are one node.
|
||||
#[test]
|
||||
fn the_ledger_root() {
|
||||
|
|
|
|||
|
|
@ -353,6 +353,9 @@ pub struct Inner {
|
|||
pub budget: Mutex<Budget>,
|
||||
pub ids: AtomicU64,
|
||||
pub summaries: Mutex<Vec<SignedSessionSummary>>,
|
||||
/// The sampler of the relay's own energy, where the host offers a counter and the
|
||||
/// configuration reads it.
|
||||
pub meter: Option<crate::energy::Meter>,
|
||||
pub going_away: AtomicBool,
|
||||
pub stats: Stats,
|
||||
/// Woken when a publisher or a session appears or leaves.
|
||||
|
|
@ -430,6 +433,10 @@ impl Relay {
|
|||
}
|
||||
None => None,
|
||||
};
|
||||
let meter = match cfg.metering.energy {
|
||||
crate::config::EnergyMode::Off => None,
|
||||
crate::config::EnergyMode::Auto => crate::energy::Meter::host(cfg.metering.dedicated_host).map(|c| crate::energy::Meter::start(c, crate::energy::INTERVAL)),
|
||||
};
|
||||
Ok(Relay {
|
||||
inner: Arc::new(Inner {
|
||||
cfg,
|
||||
|
|
@ -444,6 +451,7 @@ impl Relay {
|
|||
budget: Mutex::new(budget),
|
||||
ids: AtomicU64::new(1),
|
||||
summaries: Mutex::new(Vec::new()),
|
||||
meter,
|
||||
going_away: AtomicBool::new(false),
|
||||
stats: Stats::default(),
|
||||
change: Notify::new(),
|
||||
|
|
@ -521,11 +529,58 @@ impl Relay {
|
|||
/// The relay's per-hop session record of `room` over the sessions that ended (WAI
|
||||
/// live-metering §3), on the room clock of `t0_unix_us`, signed with the relay's key.
|
||||
/// A relay that keeps a DVR store signs version 2, with the store's figures for the
|
||||
/// room over the record's span (§3.9).
|
||||
/// room over the record's span (§3.9). Its energy is [`Self::record_energy`]'s figure,
|
||||
/// or the unmetered marker.
|
||||
pub fn hop_record(&self, room: [u8; 32], t0_unix_us: u64) -> crate::metering::HopRecord {
|
||||
let summaries: Vec<_> = self.summaries().into_iter().map(|s| s.summary).collect();
|
||||
let figures = self.inner.dvr.as_ref().map(|d| move |from: u64, to: u64| d.figures(room, from, to));
|
||||
crate::metering::hop_record(&summaries, room, t0_unix_us, &self.inner.key, figures.as_ref().map(|f| f as &dyn Fn(u64, u64) -> crate::dvr::RoomFigures))
|
||||
let rec = crate::metering::hop_record(&summaries, room, t0_unix_us, &self.inner.key, figures.as_ref().map(|f| f as &dyn Fn(u64, u64) -> crate::dvr::RoomFigures));
|
||||
match self.record_energy(&summaries, room, t0_unix_us + rec.start, t0_unix_us + rec.end) {
|
||||
Ok(f) => rec.with_energy(&f, &self.inner.key),
|
||||
Err(why) => {
|
||||
tracing::info!(room = hex::encode(room), why = why.label(), "the room's record carries the unmetered marker");
|
||||
rec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The relay's own energy over `[from_us, to_us)` (Unix microseconds), as a room's
|
||||
/// record may carry it: the meter's figure ([`crate::energy`]), where the relay has a
|
||||
/// meter and served that room alone across the span: every session that overlapped the
|
||||
/// span has ended, and carried tracks of `room` and of no other room or none.
|
||||
pub fn record_energy(&self, summaries: &[crate::summary::SessionSummary], room: [u8; 32], from_us: u64, to_us: u64) -> Result<crate::energy::Figure, crate::energy::Unmetered> {
|
||||
let meter = self.inner.meter.as_ref().ok_or(crate::energy::Unmetered::NoCounter)?;
|
||||
let room_hex = hex::encode(room);
|
||||
let overlaps = |open_ms: u64, closed_ms: Option<u64>| open_ms * 1000 < to_us && closed_ms.is_none_or(|c| (c + 1) * 1000 > from_us);
|
||||
let this_room_only = |s: &crate::summary::SessionSummary| {
|
||||
let t = &s.tally;
|
||||
let names = t.subscriber.tracks.keys().chain(t.publisher.tracks.keys());
|
||||
let mut any = false;
|
||||
for n in names {
|
||||
match crate::metering::classify(n) {
|
||||
Some((r, _)) if r == room => any = true,
|
||||
_ => return false,
|
||||
}
|
||||
}
|
||||
t.subscriber.layer_spans.iter().all(|x| x.room == room_hex) && (any || !t.subscriber.layer_spans.is_empty())
|
||||
};
|
||||
if summaries.iter().any(|s| overlaps(s.opened_unix_ms, Some(s.closed_unix_ms)) && !this_room_only(s)) {
|
||||
return Err(crate::energy::Unmetered::Shared);
|
||||
}
|
||||
if self.inner.state.lock().unwrap().sessions.values().any(|c| overlaps(c.opened_unix_ms, None)) {
|
||||
return Err(crate::energy::Unmetered::Shared);
|
||||
}
|
||||
meter.figure(from_us, to_us)
|
||||
}
|
||||
|
||||
/// Where the relay reads its own energy: the counter's scope, or `None`.
|
||||
pub fn energy_scope(&self) -> Option<crate::energy::Scope> {
|
||||
self.inner.meter.as_ref().map(crate::energy::Meter::scope)
|
||||
}
|
||||
|
||||
/// The time of the energy sampler's latest sample, Unix microseconds, once it has one.
|
||||
pub fn energy_sampled_until(&self) -> Option<u64> {
|
||||
self.inner.meter.as_ref().and_then(crate::energy::Meter::last_sample_us)
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> RelayStats {
|
||||
|
|
|
|||
|
|
@ -4,9 +4,12 @@
|
|||
//! The summary is relay-scoped telemetry (WAI `jwp-receipts.md` §4.2: "local to the relay
|
||||
//! and MUST NOT be conflated with publisher-issued receipts"), signed by the relay's own
|
||||
//! key. It counts bytes and objects. It states energy only as its acquisition class
|
||||
//! allows: no meter backs this relay, so its own figure is the unmetered marker, 0 with
|
||||
//! the class `unmetered`, which a reader must not present as zero joules (WAI
|
||||
//! `energy-measurement.md` §2). Where the door verified an object's receipt, the
|
||||
//! allows: a session's own share of the relay's energy is the unmetered marker, 0 with the
|
||||
//! class `unmetered`, which a reader must not present as zero joules (WAI
|
||||
//! `energy-measurement.md` §2). The relay's counter ([`crate::energy`]) reads the whole
|
||||
//! process, and sessions run together in it, so no read of it is one session's; a room's
|
||||
//! record carries the relay's figure where the relay served that room alone
|
||||
//! ([`crate::relay::Relay::hop_record`]). Where the door verified an object's receipt, the
|
||||
//! publisher's signed figure for producing that object is summed under the class it was
|
||||
//! signed with, and labelled as the publisher's: it is not the relay's energy.
|
||||
//!
|
||||
|
|
@ -243,7 +246,8 @@ impl Tally {
|
|||
/// The relay's own energy for the session, as its acquisition class allows.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RelayEnergy {
|
||||
/// `unmetered`: no meter backs the figure.
|
||||
/// `unmetered`: a session's share of the relay process's energy is not read, since
|
||||
/// sessions run together and the counter cannot tell their shares apart.
|
||||
pub acquisition: String,
|
||||
/// 0 is the unmetered marker, never a measurement of zero joules.
|
||||
pub joules_micro: u64,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
//! The relay's own energy in a room's per-hop session record (WAI `live-metering.md` §8,
|
||||
//! `energy-measurement.md` §2.3, §3.1), on a host whose counter the relay reads: macOS's task
|
||||
//! counter, or Linux's package counter where the configuration states the host runs the
|
||||
//! relay alone (this test states it, to read the counter; the record is not published).
|
||||
//!
|
||||
//! A publisher sends a room's video for over a second to one subscriber. The record then
|
||||
//! carries `OnChipCounter` with its evidence, reads back and verifies; the same room beside a
|
||||
//! second room's session carries the unmetered marker, as does a relay whose configuration
|
||||
//! turns the meter off. Where the host offers no counter (a container, another platform) the
|
||||
//! test prints SKIPPED and passes only if `JOULE_MOQ_RELAY_ENERGY_REQUIRE` is unset: a skip is
|
||||
//! never a pass where the counter is required.
|
||||
|
||||
mod common;
|
||||
|
||||
use common::*;
|
||||
use joule_moq_relay::config::EnergyMode;
|
||||
use joule_moq_relay::metering::HopRecord;
|
||||
use std::time::Duration;
|
||||
|
||||
const ROOM: &str = "5f3c2b1a09f8e7d6c5b4a39281706f5e4d3c2b1a09f8e7d6c5b4a39281706f5e";
|
||||
const OTHER_ROOM: &str = "a0b1c2d3e4f5061728394a5b6c7d8e9fa0b1c2d3e4f5061728394a5b6c7d8e9f";
|
||||
const GROUPS: u64 = 40;
|
||||
|
||||
fn key(i: u8) -> ed25519_dalek::SigningKey {
|
||||
ed25519_dalek::SigningKey::from_bytes(&[i; 32])
|
||||
}
|
||||
|
||||
fn required() -> bool {
|
||||
std::env::var_os("JOULE_MOQ_RELAY_ENERGY_REQUIRE").is_some()
|
||||
}
|
||||
|
||||
/// A publisher of `room` sends `GROUPS` groups over about 1.6 s to one subscriber; with
|
||||
/// `other`, a second room's publisher stays connected across it. Both close before the
|
||||
/// record is signed, and the sampler has passed the span's end.
|
||||
async fn session(server: &joule_moq_relay::RelayServer, other: bool) -> HopRecord {
|
||||
let relay = server.relay().clone();
|
||||
assert!(until(STEP, || relay.energy_sampled_until().is_some()).await, "the sampler has a first sample");
|
||||
let k = key(7);
|
||||
let owner = hex::encode(k.verifying_key().to_bytes());
|
||||
let t = Tracks::new(ns(&[ROOM, &owner, "video"]), "0", 10, 1, 0);
|
||||
let (p, _ep) = connect(server, Via::Quic, Default::default()).await;
|
||||
let _pt = publish_tracks(&p, &t, None).await;
|
||||
let second = if other {
|
||||
let (q, eq) = connect(server, Via::Quic, Default::default()).await;
|
||||
let u = Tracks::new(ns(&[OTHER_ROOM, &owner, "video"]), "0", 20, 2, 0);
|
||||
let pu = publish_tracks(&q, &u, None).await;
|
||||
Some((q, eq, pu))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (s, _es) = connect(server, Via::Quic, Default::default()).await;
|
||||
let next = joule_moq::draft22::Parameters::new().with(joule_moq::draft22::Parameter::LocationFilter(joule_moq::draft22::LocationFilter::NextObject));
|
||||
let v = subscribe_both(&s, &t, next).await;
|
||||
let publish = async {
|
||||
for g in 0..GROUPS {
|
||||
publish_group(&p, &k, &t, g, &payload("e", g, 4096)).await;
|
||||
tokio::time::sleep(Duration::from_millis(40)).await;
|
||||
}
|
||||
};
|
||||
let read = collect_concurrent(&s, STEP, Duration::from_secs(1), |got| got.iter().filter(|x| x.header.track_alias == v.media_alias).count() as u64 == GROUPS);
|
||||
let ((), got) = tokio::join!(publish, read);
|
||||
assert_eq!(got.iter().filter(|x| x.header.track_alias == v.media_alias).count() as u64, GROUPS, "every group reached the subscriber");
|
||||
s.close(0, "done");
|
||||
p.close(0, "done");
|
||||
let want = if other { 3 } else { 2 };
|
||||
if let Some((q, _, _)) = &second {
|
||||
q.close(0, "done");
|
||||
}
|
||||
assert!(until(STEP, || relay.summaries().len() >= want).await, "the sessions' summaries");
|
||||
let closed_us = relay.summaries().iter().map(|s| (s.summary.closed_unix_ms + 1) * 1000).max().unwrap();
|
||||
assert!(until(STEP, || relay.energy_sampled_until().is_some_and(|t| t > closed_us + 200_000)).await, "the sampler passed the span's end");
|
||||
let room: [u8; 32] = hex::decode(ROOM).unwrap().try_into().unwrap();
|
||||
relay.hop_record(room, 0)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn a_room_served_alone_carries_the_relays_own_energy() {
|
||||
let server = relay(|c| c.metering.dedicated_host = true).await;
|
||||
let Some(scope) = server.relay().energy_scope() else {
|
||||
assert!(!required(), "JOULE_MOQ_RELAY_ENERGY_REQUIRE is set, and this host offers the relay no counter");
|
||||
println!("SKIPPED: this host offers the relay no energy counter (macOS task counter, or a readable RAPL package counter); nothing was measured");
|
||||
server.shutdown().await;
|
||||
return;
|
||||
};
|
||||
let rec = tokio::time::timeout(Duration::from_secs(120), session(&server, false)).await.expect("in time");
|
||||
let bytes = rec.to_bytes();
|
||||
let back = HopRecord::decode(&bytes, &rec.ledger_bytes()).expect("the record reads back");
|
||||
assert_eq!(back, rec);
|
||||
rec.verify().expect("the record verifies under the relay's key");
|
||||
assert_eq!(rec.class_tag, 4, "OnChipCounter: the counter was live, the room alone ({} µJ)", rec.joules);
|
||||
assert!(rec.joules > 0);
|
||||
let ppm = u32::from_be_bytes(rec.class_evidence[..4].try_into().unwrap());
|
||||
let window = u64::from_be_bytes(rec.class_evidence[4..12].try_into().unwrap());
|
||||
assert_eq!(rec.class_evidence[12], 0, "filtering Unknown: an unprivileged reader cannot see one");
|
||||
assert_eq!(window, rec.end - rec.start, "the window is the record's span");
|
||||
assert!(rec.joules * u64::from(ppm) >= 288_676, "the resolution floor");
|
||||
assert_eq!(bytes.len(), 281 + 13 + 90 * rec.entries.len(), "tag 4's evidence, and nothing per object or per viewer");
|
||||
println!(
|
||||
"measured: {} µJ over {} µs, scope {}, relative standard uncertainty {} ppm ({:.3} W)",
|
||||
rec.joules,
|
||||
window,
|
||||
scope.label(),
|
||||
ppm,
|
||||
rec.joules as f64 / window as f64
|
||||
);
|
||||
server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn a_room_beside_another_rooms_session_carries_the_unmetered_marker() {
|
||||
let server = relay(|c| c.metering.dedicated_host = true).await;
|
||||
if server.relay().energy_scope().is_none() {
|
||||
assert!(!required(), "JOULE_MOQ_RELAY_ENERGY_REQUIRE is set, and this host offers the relay no counter");
|
||||
println!("SKIPPED: this host offers the relay no energy counter; nothing was measured");
|
||||
server.shutdown().await;
|
||||
return;
|
||||
}
|
||||
let rec = tokio::time::timeout(Duration::from_secs(120), session(&server, true)).await.expect("in time");
|
||||
assert_eq!((rec.joules, rec.class_tag, rec.class_evidence.len()), (0, 0, 0), "another room's work in the span: the relay's energy is not this room's");
|
||||
server.shutdown().await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
|
||||
async fn a_relay_whose_meter_is_off_carries_the_unmetered_marker() {
|
||||
let server = relay(|c| c.metering.energy = EnergyMode::Off).await;
|
||||
assert!(server.relay().energy_scope().is_none());
|
||||
let relay = server.relay().clone();
|
||||
let k = key(9);
|
||||
let t = Tracks::new(ns(&[ROOM, &hex::encode(k.verifying_key().to_bytes()), "video"]), "0", 10, 1, 0);
|
||||
let (p, _ep) = connect(&server, Via::Quic, Default::default()).await;
|
||||
let _pt = publish_tracks(&p, &t, None).await;
|
||||
publish_group(&p, &k, &t, 0, &payload("e", 0, 64)).await;
|
||||
p.close(0, "done");
|
||||
assert!(until(STEP, || !relay.summaries().is_empty()).await);
|
||||
let rec = relay.hop_record(hex::decode(ROOM).unwrap().try_into().unwrap(), 0);
|
||||
assert_eq!((rec.joules, rec.class_tag), (0, 0));
|
||||
assert_eq!(relay.summaries()[0].summary.relay_energy.acquisition, "unmetered", "a session's share is never read");
|
||||
server.shutdown().await;
|
||||
}
|
||||
|
|
@ -83,6 +83,9 @@ async fn run() {
|
|||
let server = relay(|c| {
|
||||
c.limits.quic_send_window_bytes = 64 * 1024;
|
||||
c.summary.key_path = Some(seed_path.clone());
|
||||
// The corpus holds this record, so its bytes must not depend on the host: its energy
|
||||
// is the unmetered marker here, and tests/relay_energy.rs reads the meter.
|
||||
c.metering.energy = joule_moq_relay::config::EnergyMode::Off;
|
||||
c.metering.grants = GRANTS
|
||||
.iter()
|
||||
.map(|&(i, g, jc, fc, cur)| MeteredGrant {
|
||||
|
|
@ -178,7 +181,7 @@ async fn run() {
|
|||
assert_eq!(back, rec);
|
||||
rec.verify().expect("the record verifies under the relay's key");
|
||||
assert_eq!(rec.hop, key(RELAY).verifying_key().to_bytes());
|
||||
assert_eq!((rec.joules, rec.class_tag), (0, 0), "the unmetered marker: no meter backs this relay");
|
||||
assert_eq!((rec.joules, rec.class_tag), (0, 0), "the unmetered marker: the meter is off for the corpus's record");
|
||||
assert_eq!(rec.to_bytes().len(), 281 + 90 * rec.entries.len(), "no class evidence, and nothing per object or per viewer");
|
||||
assert!(rec.start < rec.end);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ function corpusInPage(v) {
|
|||
const got = JSON.parse(wai.routeReport(JSON.stringify(c.report)));
|
||||
if (got.outcome !== c.outcome || (c.outcome === "routed" ? !same(got.table, c.table) : got.reason !== c.reason)) fails.push(`route ${c.name}`);
|
||||
}
|
||||
for (const c of v.choices || []) {
|
||||
n++;
|
||||
const got = JSON.parse(wai.chooseReport(JSON.stringify(c.report)));
|
||||
if (got.outcome !== c.outcome || (c.outcome === "chosen" ? !same(got.choices, c.choices) : got.reason !== c.reason)) fails.push(`choice ${c.name}`);
|
||||
}
|
||||
for (const c of v.constraints) {
|
||||
n++;
|
||||
const got = JSON.parse(wai.evaluateConstraints(JSON.stringify(c.sets), JSON.stringify(c.params)));
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
// the state of the frame on screen (§10).
|
||||
//
|
||||
// What is normative is computed by wai-live-web's wasm, the same Rust the corpus
|
||||
// live-conformance/browser holds: `routeReport`, `makeBinding`, `frameTier`, `frameState`.
|
||||
// live-conformance/browser holds: `routeReport`, `chooseReport`, `makeBinding`, `frameTier`, `frameState`.
|
||||
// This module asks the user agent what the report needs, keeps the side table over live
|
||||
// decoders and frames, and draws the state behind a debug control.
|
||||
|
||||
|
|
@ -204,7 +204,11 @@ export async function buildReport(wai, objects, { host = {}, wasm = [], constrai
|
|||
}
|
||||
}
|
||||
const routed = JSON.parse(wai.routeReport(JSON.stringify(report)));
|
||||
return { report, asked, outcome: routed.outcome, reason: routed.reason || null, table: routed.table || [] };
|
||||
// §4.7: where objects are alternatives of one content (a shared `choice`), the one to
|
||||
// decode, in SPEC §4's energy order: the user agent's power-efficient answer first, a
|
||||
// learned capability with no energy budget last.
|
||||
const chosen = routed.outcome === "routed" ? JSON.parse(wai.chooseReport(JSON.stringify(report))) : null;
|
||||
return { report, asked, outcome: routed.outcome, reason: routed.reason || null, table: routed.table || [], choices: chosen && chosen.outcome === "chosen" ? chosen.choices : [] };
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ §10: the frame on screen
|
||||
|
|
|
|||
|
|
@ -123,7 +123,11 @@ pub trait Relay: MoqEndpoint {
|
|||
/// Unregister a downstream by handle.
|
||||
async fn unregister_downstream(&mut self, handle: u64) -> MoqResult<()>;
|
||||
|
||||
/// Joules consumed by this relay forwarding objects (used for joule
|
||||
/// receipts on egress).
|
||||
/// Microjoules this relay has accounted for forwarding objects. `0` is the
|
||||
/// unmetered marker (WAI energy-measurement §2), never a figure of zero joules,
|
||||
/// and a caller shows it as unmetered. The figure carries no acquisition class:
|
||||
/// it is what the implementation was told to account (`MockRelay::meter_egress`),
|
||||
/// not a reading, and is never presented as a measurement. A relay's measured
|
||||
/// energy, with its class, is `joule-moq-relay`'s record of a room.
|
||||
fn relay_joules_consumed_micro(&self) -> u64;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -623,7 +623,9 @@ impl MockRelay {
|
|||
}
|
||||
}
|
||||
|
||||
/// Account for joules consumed forwarding an object.
|
||||
/// Account `joules` µJ to forwarding an object: a figure the caller supplies, with
|
||||
/// no class and no reading behind it. Nothing in this crate calls it, so the mock's
|
||||
/// figure stays the unmetered marker unless a test supplies one.
|
||||
pub fn meter_egress(&self, joules: u64) {
|
||||
self.joules_consumed.fetch_add(joules, Ordering::SeqCst);
|
||||
}
|
||||
|
|
|
|||
2
wai/.gitignore
vendored
2
wai/.gitignore
vendored
|
|
@ -53,6 +53,8 @@ wai-web/demo/pkg/
|
|||
wai-web/demo/pkg-node/
|
||||
# The WebGPU member's bundle (demo/quantum_webgpu.html; built with --features quantum_webgpu).
|
||||
wai-web/demo/pkg-webgpu/
|
||||
# The platform decode bundle (demo/platform_decode.html; built with --features platform_decode).
|
||||
wai-web/demo/pkg-platform/
|
||||
# int_hyper conditional-codec demo artifacts (PTQ model + bitstream, large —
|
||||
# regenerated by tools/wai_sota_int_synth_export.py into demo/int_hyper/).
|
||||
wai-web/demo/int_hyper/
|
||||
|
|
|
|||
247
wai/CHANGELOG.md
247
wai/CHANGELOG.md
|
|
@ -6,6 +6,101 @@ wire format may change and such changes are recorded here.
|
|||
|
||||
## v0.4.0 — current
|
||||
|
||||
### Energy honesty: the energy order of dispatch, a metered relay, `HwShunt` closed to new profiles, and what fewer bytes do not save
|
||||
|
||||
- **The energy order** (SPEC §4, §7.1 step 2). A sink tries the capabilities an envelope
|
||||
offers in the energy order unless its deployer configures another, an order over what it
|
||||
advertises and never a reason to advertise: `first` for classical decode the platform
|
||||
answers supported, smooth and power-efficient, `smooth`, `classical` (any other classical
|
||||
decode, every classical capability of a sink with no platform to ask, and a learned
|
||||
capability whose decode the sink holds a qualifying measurement of), and
|
||||
`learned-without-budget` last; the envelope's order within a rank. A `WAI1`'s fallback is
|
||||
tried first when it ranks before the capability, and the capability after it if it does
|
||||
not decode, so the order never makes inert an envelope either could decode. In a `WAI2`
|
||||
the entries never picked keep their places. So a learned capability without a
|
||||
qualifying measurement is never decoded above a classical fallback the sink can decode.
|
||||
A sink that takes the manifest's order says so where it reports a decode.
|
||||
- **The measurement a learned capability needs** is a sink-metered figure of its decode, in
|
||||
browser-sink §6's energy entry: it qualifies with the class `OnChipCounter` or
|
||||
`CalibratedInstrument` and the coverage `Whole`. The order reads only whether one is held,
|
||||
never its value, so a figure still changes no route (browser-sink §6). A deployer may
|
||||
compare it with a ceiling per decode as its own policy; the default does not.
|
||||
- **The browser sink** (browser-sink §4.7): objects that share a `choice` are alternatives
|
||||
of one content (a `WAI2`'s entries, a `WAI1`'s capability and fallback, a track's
|
||||
renditions), ordered inside the routing: an alternative routed `ua` ranks by the user
|
||||
agent's answer, one routed `host` or `wasm` ranks `classical` whatever the user agent said
|
||||
(AV1's user-agent tier is `none`, and a decode-equivalence need with no probe run routes
|
||||
elsewhere), and one routed `none` comes last; the page decodes the first that routes
|
||||
(`no_alternative` where none does). `browser_sink::choose_report`, the page's
|
||||
`chooseReport`, and a `choices` section in `live-conformance/browser/` with each
|
||||
alternative's rank and synthetic measurements marked as such, checked by `wai-rs`, by the
|
||||
page's wasm build in a browser and by `browser_sink_verify.py`.
|
||||
- **Platform decode capabilities** ([platform-decode](extensions/platform-decode.md)): the
|
||||
query a page asks Media Capabilities and WebCodecs, read from the entry's payload (the AV1
|
||||
ISOBMFF binding's codec string from the sequence header, the Annex E.3 string from an
|
||||
HEVC SPS), never from what an encoder declared; and the answer entered in browser-sink's
|
||||
report, `supported` from `isConfigSupported()` alone and `smooth` and `power_efficient`
|
||||
from `decodingInfo()` once it said supported, so a Media Capabilities `supported: false`
|
||||
overrides nothing. `powerEfficient` and `prefer-hardware` are hints: never an energy
|
||||
figure, never evidence of hardware. The extension defines no order and no second meaning
|
||||
of advertising: both are browser-sink's. `wai::platform_decode` (`query`,
|
||||
`report_answer`), `platform-decode-conformance/queries.json` (two packaging-corpus floors
|
||||
and an SPS the platform's encoder wrote with `prefer-hardware`), and `wai-web`'s
|
||||
`platform_decode` bundle (`platformQuery`, `platformAnswer`, `platformChoose`), now built
|
||||
in CI. In a browser on an arm64 desktop, every AV1 floor of the packaging corpus decoded
|
||||
through WebCodecs with its payload's configuration, and an HEVC stream from the platform's
|
||||
encoder decoded back with the codec string read from the stream.
|
||||
- **Reference.** `wai-rs` `energy_order` (`DispatchPolicy`, `PlatformAnswer`, `Rank`,
|
||||
`read_measurement`, `qualifies`); `Wai::dispatch_verified` and `WaiMulti::pick` apply the
|
||||
energy order, `dispatch_with` and `pick_with` take another, and the HTTP client's default
|
||||
order and the C ABI's `wai_envelope_select` follow it. The HTTP corpus's O2 pin cases and
|
||||
the pin corpus's selection cases state the table's order they run in; `select_O2_default`
|
||||
and `fetch_O2_default` are the default.
|
||||
- **`HwShunt` is closed to new profiles** (energy-measurement §2.6, *New profiles*): a
|
||||
profile, record, claim, table or registration member defined after the hardware class was
|
||||
refined refuses `HwShunt` from every producer and reads it as no other class; the profiles
|
||||
that could have sealed it keep their acceptance, listed by name. `wai-rs`
|
||||
`energy_class::check_new_profile` (`LegacyInNewProfile`). A package counter read through
|
||||
RAPL or a system management unit is `OnChipCounter`.
|
||||
- **The reference relay meters itself** (live-metering §8). `joule-moq-relay` samples a
|
||||
counter every 100 ms: its process's own CPU energy on macOS (scope task), or the CPU
|
||||
packages' RAPL energy on a Linux host the operator states runs it alone (scope package).
|
||||
A room's record carries the advance over its span as `OnChipCounter` with filtering
|
||||
`Unknown` and a declared uncertainty, where the counter was live and the relay served
|
||||
that room alone; the unmetered marker otherwise. A session's summary stays unmetered.
|
||||
`[metering] energy = "auto" | "off"` and `dedicated_host`; a macOS CI job requires a
|
||||
measured figure, and a host with no counter prints SKIPPED.
|
||||
- **No modelled or zero figure shown as measured.**
|
||||
- The C settlement kernel (`oer-conformance/settle/`) graded every receipt `measured`
|
||||
from `hardware` whatever its meter; it now writes the grade and device the meter states.
|
||||
The host harness's figures are constants, so they are `projected`, from a `simulation`,
|
||||
by `model_estimated`, and `firmware_vectors.json` is regenerated.
|
||||
- `wai-web`: the world demo seals its page-declared estimate labelled `Estimator` and
|
||||
shows it as one; the quantum demos seal the unmetered marker in place of invented
|
||||
constants.
|
||||
- The quantum studio shows a figure with its class, coverage and uncertainty; the quantum
|
||||
CLI prints the unmetered marker as unmetered, not `0 uJ`; the metering tools head their
|
||||
bracket as a bracket and print the unmetered marker on stdout when they seal it; a
|
||||
provenance total names the class it reads at.
|
||||
- The reference meters (`wai_meter`, `wai_video_meter`, `wai_world_meter`,
|
||||
`wai_quantum_meter`, `wai_prov`, and `wai_stage_measure --energy macmon`) sealed
|
||||
`OnChipCounter` from the processor power a non-privileged reader reports, a channel
|
||||
they never read. On macOS 27 the processor channels of IOReport's `Energy Model` group
|
||||
stop updating for such a reader, and the reader takes its power from another report it
|
||||
selects. The meters now seal `ModelBased`, naming the reader in the measurement report
|
||||
as the method. `energy_burst::PowerSource` says what a reader's samples are, and
|
||||
`Bracket::on_chip_figure` refuses `OnChipCounter` from a reported power whatever its
|
||||
brackets show (energy-measurement §3.1, §2.9). A RAPL reader's bracket, the counter's
|
||||
own differences, still seals `OnChipCounter`. No committed record changes class: the
|
||||
provenance vector's two figures are unlabelled and keep their bytes.
|
||||
- **What fewer bytes do not save.** Steering by joules per delivered byte (energy-steering
|
||||
§5.5), the attribution of a hop's joules to grants by bytes (live-metering §4.6,
|
||||
live-rooms' `joules_ceiling`) and an HTTP client's joule budget (http-delivery
|
||||
Appendix F) now say what their figures cover: a hop's own energy or a producer's, not the
|
||||
network's, which is largely flat against the traffic it carries. The honest gains of
|
||||
fewer bytes are cost and a mobile radio's draw; the energy that changes is the sink's
|
||||
decode path and display.
|
||||
|
||||
### `wai.age.assurance`: the room's own service under the serving rule
|
||||
|
||||
- **The rule** ([age-assurance](extensions/age-assurance.md) §5.5): a session that proves
|
||||
|
|
@ -173,6 +268,158 @@ wire format may change and such changes are recorded here.
|
|||
[`live-conformance/synthesis-mark/`](live-conformance/synthesis-mark/), and its second
|
||||
implementation `synthesis_mark_verify.py` with `_wai_mark.py`, which mutates the corpus and
|
||||
weakens its own verifier in turn. Audio is not reached in this revision.
|
||||
### GPUs: when `decode-equivalence` may be claimed, and WGSL's integer semantics on every adapter
|
||||
|
||||
- **Claiming the tier on a GPU** ([determinism-tiers](extensions/determinism-tiers.md)
|
||||
§5). A decoder claims `decode-equivalence` for output whose graphs ran on a GPU only
|
||||
when every shader module that ran them is integer only (no floating-point type, so no
|
||||
reassociation or fusion can change a result), uses no subgroup or quad operation and
|
||||
enables no extension, has no result that depends on scheduling (one atomic kind, its
|
||||
result unused; no out-of-bounds access), and is translated with the translator's integer
|
||||
guards on: signed arithmetic through unsigned, guarded division and remainder, `abs` and
|
||||
negation of the most negative value. Where the translator does not produce WGSL's
|
||||
result, the module does not reach the operation: naga 30.0.1 passes shift amounts to
|
||||
MSL, HLSL and SPIR-V unreduced and writes integer `clamp` as MSL's `clamp`, so every
|
||||
shift amount is masked below 32 and every `clamp` has literal bounds in order. Before an
|
||||
adapter runs a graph it passes WGSL's integer semantics vectors and the implementation's
|
||||
probe; the claim names the adapter and the translator.
|
||||
- **WGSL's integer semantics vectors** (`wgsl-conformance/`). Every integer operation WGSL
|
||||
defines on `i32`, `u32` and their two-component vectors, at the cases WGSL defines at run
|
||||
time and MSL, HLSL and SPIR-V leave open, each result written from the WGSL text
|
||||
(Candidate Recommendation Draft, 21 October 2026). Cases the kernels never reach are
|
||||
marked `unguarded`: reported, not deciding.
|
||||
- **`wai-ops-gpu`.** Discovery runs the semantics suite on each adapter after the
|
||||
arithmetic probe, and refuses an adapter that differs on a guarded case. Every run-time
|
||||
shift amount in the kernels is now masked (`& 31u`) and the two run-time clamps are
|
||||
`min(max(…))`; WGSL gives the same result either way, and the translated code is then
|
||||
defined on every backend. `tests/wgsl_rules.rs` holds every shipped module to the
|
||||
conditions on naga's IR and in its MSL and HLSL translations, with planted violations
|
||||
refused; the browser page runs the suite through the browser's WGSL compiler and checks
|
||||
the vectors against the reference compiled to wasm32. Measured on Metal, a browser's
|
||||
WebGPU, Vulkan on two vendors' GPUs on Linux and Windows, and DX12 on both on Windows:
|
||||
every guarded case equal, every unguarded one too, and the capability corpora decoding
|
||||
with their graphs on the device to the CPU decode's bytes.
|
||||
|
||||
### Packaging: each step on the fabric that does it with the least energy
|
||||
|
||||
- **Steps and fabrics** ([packaging](extensions/packaging.md) §11.6). The computations of
|
||||
a pack and a verify are named steps (`source-read`, `segment`, `base-search`,
|
||||
`base-code`, `refine-code`, `sse`, `object-hash`, `digest`, `floor-read`,
|
||||
`floor-decode`, `index`, `io`, and `source-decode` before a pack), and each may run on a
|
||||
CPU, a GPU, a media engine or a neural engine. Every step is defined in integers (and
|
||||
AV1 decoding by its decoding process), so where it runs changes no byte of a package
|
||||
and no verdict: §11.2 holds across fabrics. A packager runs a step on a backend only
|
||||
after it has given the reference's output on qualifying inputs; a verifier computes a
|
||||
check on another fabric only where the package states the value it compares with, and
|
||||
decides any mismatch again on the reference. No hardware encoder computes a WAI step; a
|
||||
floor encoded on one is a §7.5 floor. A floor states no decoded-output digest, so a
|
||||
hardware decode never decides a floor verdict.
|
||||
- **The fabric record** ([jwp-receipts](extensions/jwp-receipts.md) §2.4.8,
|
||||
[energy-measurement](extensions/energy-measurement.md) §6.3). A measurement claim may
|
||||
carry `fabrics`: each step with every fabric that ran it and its backend, and the
|
||||
figure divided into one share per fabric, attributed `task` (the operating system's
|
||||
account of the work's own energy) or `rail` (the fabric's whole rail, the work its sole
|
||||
user). A rail shared with other work is never divided by busy time. `fabrics` is the one
|
||||
claim member that is absent rather than null, so every existing claim and package keeps
|
||||
its bytes. New reasons: `measurement_fabric_unknown`, `measurement_fabric_order`,
|
||||
`measurement_fabric_unmetered`, `measurement_fabric_unused`, `measurement_fabric_sum`.
|
||||
- **wai-pack** places each call of a step on the fabric whose measured cost for its size
|
||||
is least (`--fabric cpu|gpu|auto:<costs.json>`, `wai-pack fabric list|calibrate`), the
|
||||
CPU on a tie, where unmeasured, and for every call another fabric does not take. With
|
||||
feature `gpu`, the motion search, SSE and the object hashes run on a GPU through
|
||||
**wai-pack-gpu**, exact integer WGSL kernels through wgpu (Metal, Vulkan, DX12). With
|
||||
feature `fabric-meter` on macOS, `--meter fabric` meters a pack per fabric without
|
||||
privileges and writes the claim's fabric record. `wai-pack floor-decode` decodes a
|
||||
package's floors with libdav1d or the media engine and prints each piece's digest, for
|
||||
comparing decoders. The Python verifiers stay on the CPU and share no code with wai-rs;
|
||||
`claim_verify.py` and `pack_verify.py` read the fabric record.
|
||||
- **Conformance.** Claim vectors of the fabric record appended after the existing ones, which a test
|
||||
pins by digest. wai-pack-gpu's kernels are held to references transcribed from the text
|
||||
and every edited kernel is caught; wai-pack's fabric differential packs every golden on
|
||||
a GPU byte for byte and gives every package and staged mutation the CPU's verdict.
|
||||
|
||||
### Packaging: a title's whole ladder in one package
|
||||
|
||||
- **Several floor rungs per package** ([packaging](extensions/packaging.md) §7.1). A
|
||||
component may have any number of floor rungs; each piece then has one floor object per
|
||||
floor rung, in ladder order, all over one base, so the base of a ladder is coded once.
|
||||
Each floor rung has a **floor name**: the component's id when it has one floor rung (so
|
||||
existing packages are unchanged, byte for byte), the rung's id when it has several. The
|
||||
name names the supplied stream (`<name>.obu`, §7.3), the CMAF files (`floor/<name>/`,
|
||||
§7.6), the floor playlist (`hls/classic/floor-<name>.m3u8`), the DASH representation
|
||||
(`<name>-floor`) and the MoQ track (`<name>/floor`); a floor name that cannot be one path
|
||||
segment, or two equal in any case, is refused (`pack-id`). The classical master lists one
|
||||
variant per floor rung; with several, `dash/classic.mpd` carries each representation's
|
||||
codecs and size on the representation. The verifier holds the *k*-th floor entry of each
|
||||
piece to the *k*-th floor rung.
|
||||
- **A re-pack from a parent** (§11.5). A packer may take every piece's base and
|
||||
refinements from an earlier package instead of coding them (`wai-pack pack
|
||||
--base-from <package>`), so a title's integer base is coded once and taken by every
|
||||
package made from the same source after it. It refuses a parent that does not verify
|
||||
(`repack-parent-invalid`), one whose pieces, kinds, rungs, parameters or deadlines are
|
||||
not this pack's (`repack-parent-mismatch`), and one whose prefixes do not reconstruct
|
||||
this source with the SSE their entries state (`repack-source-mismatch`). Its receipt
|
||||
names the parent with parent kind `re-pack`, and its other files are those of a pack
|
||||
that codes. A verifier holding the parent checks the re-pack against it (`wai-pack
|
||||
verify --parent`).
|
||||
- **A floor that is the ladder's own CMAF segments** (§7.7). With
|
||||
`floor.supplied_form = "cmaf"` (absent, the OBU form; existing policy digests are
|
||||
unchanged), a supplied floor is an existing ladder's `init.mp4` and media segments, one
|
||||
segment per piece, read box by box: the floor's temporal units are the samples, each
|
||||
with a temporal delimiter before it. The package's floor files are the ladder's files,
|
||||
byte for byte, named by their SHA-256, and the package carries the supplied floor's
|
||||
listing (`floor.listing`), whose SHA-256 the policy pins, so the receipt binds every
|
||||
byte of the ladder. A server can hold those files once for the classic site and the
|
||||
package. New refusals: `floor-cmaf-malformed`, `floor-cmaf-timing`, `floor-cmaf-pieces`.
|
||||
`wai-pack pack --link-supplied` writes them as hard links to the supplied files.
|
||||
- **A floor-only profile** (§7.8). A component of `profile = "floor-only"` is its floor
|
||||
rungs alone: no integer base, no refinement, no source (`policy-floor-only`
|
||||
otherwise). Its pieces are its first floor rung's media segments (the CMAF form) or
|
||||
shown key frames (the OBU form), each at most `max_ms` (`floor-piece-length`); each piece
|
||||
is its floor objects, which present no base and so carry no `derived_from`, and its kind
|
||||
is `none` (stage index kind 6). It attests delivery: the receipt binds every floor
|
||||
object and the supplied floor's bytes, and the verifier holds them to the AV1 stream's
|
||||
structure and the CMAF files; it states no decoded output, no fidelity to a source and
|
||||
no primary. The stage index now requires a floor's `derived_from` exactly when its piece
|
||||
has a base (`floor-derived-from`).
|
||||
- **The independent verifier at scale** (`pack_verify.py`). Each object is hashed once,
|
||||
and `http_verify.resolve_uri` finds an object by its content id rather than by hashing
|
||||
every object for every URI. `--jobs N` runs every object's hash and every base's decode
|
||||
and layers in N worker processes, piece by piece, ahead of checks that are unchanged in
|
||||
order and reason. `--native-blake3` takes BLAKE3 from the `blake3` module after it
|
||||
agrees with the pure implementation on the published vectors (opt-in: that module is
|
||||
the implementation wai-rs links). `--package <dir>` verifies one package without its
|
||||
sources or its key. **The measurement claim is checked** (§9.4): the claim corpus's
|
||||
verifier reads it, and it is held to the receipt's figure, class, evidence and signer, so
|
||||
a correctly metered package passes; `package-mutations/` gains a metered package (`ok`),
|
||||
a claim about another figure (`measurement-claim-invalid`) and none
|
||||
(`measurement-claim-missing`).
|
||||
- **§7.3: key-frame sequence headers** (an informative note). A floor taken from an
|
||||
existing ladder needs a sequence header in every key-frame unit; a producer that puts
|
||||
one back must put back the stream's own first sequence header, byte for byte, not the
|
||||
sample entry's `av1C` header, which can differ in its tool flags and is refused
|
||||
(`floor-av1-malformed`). `tests/refusals.rs` holds the rule.
|
||||
- **The DASH renderer** (`wai::http_binding::dash`) writes a representation's own
|
||||
`@codecs`, `@width` and `@height` when given, and an adaptation set's `@codecs` only when
|
||||
every representation shares it.
|
||||
- **A streaming on-demand packer** (§11.4). The packer reads the source twice, a frame
|
||||
at a time, once to cut it and once to code it, and each floor stream a temporal unit at
|
||||
a time, and `wai-pack pack` writes each file into its directory as it is made, reading
|
||||
objects back for the catalogs. What it holds no longer grows with the title, but for a
|
||||
record per piece and per entry and a byte per frame per floor; a refusal removes what it
|
||||
wrote. `--jobs <n>` codes n pieces at once, each alone, so the package is the same for
|
||||
any n. A source that does not read the same twice is refused (`pack-source-changed`).
|
||||
`tests/pack_alloc.rs` packs a source eight times as long in the same peak memory.
|
||||
- **Conformance.** `packs/video-ladder-01/` (two floors, one at half size, over
|
||||
`video-base-01`'s bases, which are the same objects), `ladder-mutations/`,
|
||||
`packs/video-repack-01/` (the ladder re-packed from `video-base-01`),
|
||||
`packs/video-cmaf-01/` and `cmaf-mutations/` (the ladder's streams as a muxer's fMP4
|
||||
segments), `packs/video-floor-only-01/` and `-02/` (both forms, no base), index cases of
|
||||
a piece of floors alone, `tests/ladder.rs`, `tests/repack.rs`, `tests/cmaf.rs`,
|
||||
`tests/floor_only.rs`, and the independent
|
||||
verifier `pack_verify.py`, with a box reader of its own, which re-derives every golden
|
||||
from its sources and checks the re-pack against its parent. `supplied_floor.py` writes
|
||||
one stream per floor rung; `supplied_cmaf.py` the CMAF form.
|
||||
|
||||
### `wai.media.exploration`: archives that replay, default-distance, a homogenization meter, and taste as a declared, owned objective
|
||||
|
||||
|
|
|
|||
71
wai/SPEC.md
71
wai/SPEC.md
|
|
@ -484,6 +484,52 @@ against the wrong parameters yields plausible output, not an error. An encoder
|
|||
that needs an alternative for sinks that hold another parameter set carries one
|
||||
pinned rendition per set in a `WAI2` envelope (§7.1).
|
||||
|
||||
**Energy order.** Steps 3 and 5 are tried in the **energy order**, which a sink
|
||||
applies unless its deployer configures another. It is an order over what the sink
|
||||
advertises, and never a reason to advertise or not: what a sink advertises is
|
||||
decided as above (in a web page, by the object's route, below). Each capability
|
||||
ranks, lowest first:
|
||||
|
||||
1. `first`: classical decode the sink's platform answers supported, smooth and
|
||||
power-efficient (a page's Media Capabilities `decodingInfo()` once WebCodecs
|
||||
`isConfigSupported()` has answered supported; a native platform's own
|
||||
decoder query; [platform-decode](extensions/platform-decode.md) §2, §3). In a
|
||||
page the answer counts only for an object routed to the user agent's decoder
|
||||
(browser-sink §4.7). `powerEfficient` is the user agent's hint: it orders,
|
||||
and is never an energy figure or evidence of hardware;
|
||||
2. `smooth`: classical decode the platform answers smooth, not power-efficient;
|
||||
3. `classical`: any other classical decode the sink can make, every classical
|
||||
capability of a sink with no platform to ask among them; and a learned
|
||||
capability whose decode the sink holds a qualifying measurement of (below);
|
||||
4. `learned-without-budget`: a learned capability — one whose replicate class
|
||||
(§7) is not one of the three standard classes, or a string this revision does
|
||||
not register — with no such measurement.
|
||||
|
||||
Where `fb` ranks before `cap`, the sink takes steps 4 and 5 before step 3, and
|
||||
step 3 only if step 5 does not decode; otherwise it takes them as listed. So a
|
||||
learned capability without a qualifying measurement is never decoded above a
|
||||
classical fallback the sink can decode, and a decode the platform answers smooth
|
||||
or power-efficient comes before a software one. The order never makes inert an
|
||||
envelope either capability could decode, and it changes nothing else: an inert
|
||||
envelope reports the reason kept at step 3, else the one kept at step 5,
|
||||
whichever was tried first. A sink whose deployer configures the manifest's order
|
||||
instead takes the steps as listed, and says so wherever it reports a decode (a
|
||||
capability report, a receipt's notes, its logs). A manifest states no energy,
|
||||
and a figure in a receipt is the producer's (encode) energy, not the sink's
|
||||
decode, so the order reads neither.
|
||||
|
||||
**The measurement a learned capability needs** is a sink-metered figure of its
|
||||
decode on this sink, signed by the sink's meter with its class, its declared
|
||||
uncertainty, its coverage and the fabrics it covers, in the shape of
|
||||
[browser-sink](extensions/browser-sink.md) §6's energy entry. It qualifies when
|
||||
it reads under §6 there with the class `OnChipCounter` or `CalibratedInstrument`
|
||||
and the coverage `Whole`. The order reads only whether such a figure is held,
|
||||
never its value: a figure changes no route and steers no choice (browser-sink
|
||||
§6). A deployer MAY set a policy of its own that also compares the figure, per
|
||||
decode, with a ceiling; the default sets none. The reference dispatcher applies
|
||||
the order in `Wai::dispatch_verified` and `WaiMulti::pick`, takes another through
|
||||
`dispatch_with` and `pick_with`, and ranks with `energy_order::DispatchPolicy`.
|
||||
|
||||
A capability is **dispatchable** unless this revision registers it as something
|
||||
a sink does not decode on its own: a candidate (§5, *Candidate capabilities*), a
|
||||
companion-only capability (§5, *Companion-only capabilities*), a refinement-only
|
||||
|
|
@ -563,7 +609,11 @@ that object when the route is not `none`. It evaluates the capability's constrai
|
|||
([capability-constraints](extensions/capability-constraints.md)) before dispatch, reports a
|
||||
measured figure per decode beside the user agent's own power-efficiency answer, which stays
|
||||
the user agent's, and binds every frame it decodes to the receipted object it came from
|
||||
(browser-sink §7). The algorithm above is unchanged.
|
||||
(browser-sink §7). Where it holds several objects that are alternatives of one
|
||||
content (a `WAI2` envelope's entries, or a `WAI1` envelope's capability and
|
||||
fallback), it decodes the one the energy order above puts first among those it can
|
||||
route, the order sitting inside the routing and changing no route (browser-sink
|
||||
§4.7). The algorithm above is unchanged.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -1007,8 +1057,12 @@ The room authority, or a key its origin's key log lists with `live/meter`, signs
|
|||
receipt, `WMR1`, naming the records, the takes and the composition receipts it aggregates and
|
||||
stating publisher-seconds, subscriber-seconds by layer class, composition-seconds, events sent
|
||||
and delivered and recorded-seconds, every hop's joules with its class and a total per class,
|
||||
never one across classes, and each grant's consumption against its joule and funds ceilings;
|
||||
a verifier recomputes every number from the records. Clients' QoE observations ride the CMCD
|
||||
never one across classes, and each grant's consumption against its joule and funds ceilings,
|
||||
a hop's joules attributed to grants by the bytes each moved, an accounting convention and not a
|
||||
saving any grant made; a verifier recomputes every number from the records. The reference relay
|
||||
signs its own energy read from the host's counter (its process's per-task energy on macOS, the
|
||||
CPU packages' on a Linux host it runs alone), `OnChipCounter` with its uncertainty, where the
|
||||
counter was live and the relay served the room alone, and the unmetered marker otherwise. Clients' QoE observations ride the CMCD
|
||||
event timeline, and only a cell of at least fifty sessions reaches the receipt. The receipt maps
|
||||
onto C2PA's `c2pa.environmental-sustainability` assertion where one class covers the session
|
||||
whole: [`extensions/live-metering.md`](extensions/live-metering.md).
|
||||
|
|
@ -1030,7 +1084,10 @@ second at the floor tier, from those records with their acquisition class, compa
|
|||
only within a class, ranks a pathway whose energy is not known (an unmetered hop, two
|
||||
classes, stale records) after every pathway whose energy is, holds a client against a figure
|
||||
within a margin, and signs a steering decision, `WSD1`, naming the records, the client's
|
||||
report, its own inputs and the output, which a verifier recomputes. The output is the steering
|
||||
report, its own inputs and the output, which a verifier recomputes. The figure is the hops' own
|
||||
energy averaged over their delivery, not the network's, which is largely flat against the
|
||||
traffic it carries: a pathway chosen by it spends what its hops spend, and no claim is made that
|
||||
moving a client lowers a network's energy. The output is the steering
|
||||
manifest HLS and DASH players already read, written byte for byte, with the decision's URI in
|
||||
one member players ignore: [`extensions/energy-steering.md`](extensions/energy-steering.md).
|
||||
|
||||
|
|
@ -2663,7 +2720,11 @@ A v1.1-aware sink MUST:
|
|||
entry carries one and a pin applies to its capability (§3.1), resolves at the
|
||||
sink. An entry whose payload digest does not match, or whose pin is refused, is
|
||||
skipped as if the sink did not advertise its capability, and the sink keeps the
|
||||
reason. A deployer policy MAY reorder or restrict the entries the sink
|
||||
reason. Without a deployer policy, the sink walks the entries in §4's energy
|
||||
order: the entries step 2 may pick, by §4's ranks, table order within a rank,
|
||||
each taking one of those entries' places in the table; an entry step 2 never
|
||||
picks (a companion, a refinement, a capability that is not dispatchable) keeps
|
||||
its own place. A deployer policy MAY reorder or restrict the entries the sink
|
||||
considers; it MUST NOT pick an entry skipped for its digest or its pin.
|
||||
3. Read the corresponding rendition's bytes from the payload block.
|
||||
4. Dispatch as in v1.0 §4, with the entry's `capability` and `prior` in place of
|
||||
|
|
|
|||
|
|
@ -136,6 +136,14 @@ the figure with its class. The vectors cover:
|
|||
- **The labelled delivery leaf:** an `energy` claim against an object whose leaf carries
|
||||
a class, agreeing, and disagreeing in figure, class or evidence; an `OnChipCounter`
|
||||
label with no filtering state reads as `Unknown`.
|
||||
- **The fabric record (§2.4.8 rule 6, energy-measurement §6.3),** appended after every
|
||||
earlier vector, which a test pins by digest: accepted, a figure divided between the CPU
|
||||
(`task`) and a GPU (`rail`) over steps that ran on each; at step 2, a record written
|
||||
`null` (a report without one has no member), a step with a member it does not define,
|
||||
an empty backend and a share written as a string; at step 4, a fabric and an
|
||||
attribution outside §6.3, a share divided by GPU time (which names no attribution),
|
||||
steps out of order, a fabric twice, no step, a step on an unmetered fabric, a share on
|
||||
a fabric no step ran on, and shares that do not sum to the figure.
|
||||
|
||||
## What a party observed of a stage set: `stage` and `delivery-gap`
|
||||
|
||||
|
|
|
|||
|
|
@ -220,6 +220,13 @@ GAP_REASONS = ("delivery-timeout", "discretion", "not-received") # §2.4.11
|
|||
BRACKET = ["idle", "busy", "window_us", "completions", "in_flight", "opening"]
|
||||
SUMMARY = ["n", "mean_uw", "sd_uw", "live_intervals", "longest_live_run"]
|
||||
BUILD = ["profile", "toolchain", "target"]
|
||||
# §2.4.8's fabric record (energy-measurement §6.3): the one member of a claim that is absent
|
||||
# rather than null, so a report sealed before it existed keeps its bytes.
|
||||
FABRIC_RECORD = ["steps", "shares"]
|
||||
FABRIC_STEP = ["step", "fabric", "backend"]
|
||||
FABRIC_SHARE = ["fabric", "joules_micro", "attribution"]
|
||||
FABRICS = ["cpu", "gpu", "media", "neural"]
|
||||
ATTRIBUTIONS = ["rail", "task"]
|
||||
PREDICTION = ["predictor", "descriptor_hash", "predicted_joules_micro"]
|
||||
# energy-measurement §6.1: profile -> (subject_type, carries several figures, an Ed25519
|
||||
# key signs the figure, names a group).
|
||||
|
|
@ -399,7 +406,7 @@ def check_body(cls, b):
|
|||
else:
|
||||
raise Reject(2, "field_type")
|
||||
return
|
||||
members(b, BODY[cls], 2)
|
||||
members(b, BODY[cls] + (["fabrics"] if cls == "measurement" and "fabrics" in b else []), 2)
|
||||
if cls == "reconstruct-session":
|
||||
check_session_body(b)
|
||||
return
|
||||
|
|
@ -489,6 +496,8 @@ def check_body(cls, b):
|
|||
need(bd["toolchain"] is None or isinstance(bd["toolchain"], str), 2)
|
||||
need(bd["target"] is None or is_token(bd["target"]), 2)
|
||||
need(b["power_reader"] is None or isinstance(b["power_reader"], str), 2)
|
||||
if "fabrics" in b:
|
||||
check_fabric_shape(b["fabrics"])
|
||||
else:
|
||||
need(is_uri(b["vocabulary"]), 2)
|
||||
for k in ("op_requested", "op_committed", "op_delivered"):
|
||||
|
|
@ -1410,6 +1419,46 @@ def acquisition(b):
|
|||
need(u is None and f is None and ref is None, 4, ev)
|
||||
|
||||
|
||||
def check_fabric_shape(f):
|
||||
"""§2.4.8's fabric record at step 2: an object of `steps` and `shares`, every row of
|
||||
one an object of its members before any row's values, the terms tokens, a backend a
|
||||
non-empty string, a share's figure an integer. Never null."""
|
||||
need(isinstance(f, dict), 2)
|
||||
members(f, FABRIC_RECORD, 2)
|
||||
for key, cols in (("steps", FABRIC_STEP), ("shares", FABRIC_SHARE)):
|
||||
rows = f[key]
|
||||
need(isinstance(rows, list), 2)
|
||||
for row in rows:
|
||||
need(isinstance(row, dict), 2)
|
||||
members(row, cols, 2)
|
||||
for row in rows:
|
||||
if key == "steps":
|
||||
need(all(isinstance(row[k], str) for k in cols), 2)
|
||||
need(is_token(row["step"]) and is_token(row["fabric"]) and row["backend"] != "", 2)
|
||||
else:
|
||||
need(isinstance(row["fabric"], str) and isinstance(row["attribution"], str), 2)
|
||||
need(is_token(row["fabric"]) and is_token(row["attribution"]) and is_int(row["joules_micro"]), 2)
|
||||
|
||||
|
||||
def fabric_rules(f, joules):
|
||||
"""§2.4.8 rule 6: the vocabularies of energy-measurement §6.3, the order (steps by
|
||||
term then fabric, shares by fabric, each once, neither empty), every fabric a step
|
||||
names metered, every share's fabric running a step, and the shares summing to the
|
||||
figure."""
|
||||
st, sh = f["steps"], f["shares"]
|
||||
need(all(s["fabric"] in FABRICS for s in st) and all(s["fabric"] in FABRICS and s["attribution"] in ATTRIBUTIONS for s in sh),
|
||||
4, "measurement_fabric_unknown")
|
||||
keys = [(s["step"].encode(), FABRICS.index(s["fabric"])) for s in st]
|
||||
ranks = [FABRICS.index(s["fabric"]) for s in sh]
|
||||
need(st and sh and all(a < b for a, b in zip(keys, keys[1:])) and all(a < b for a, b in zip(ranks, ranks[1:])),
|
||||
4, "measurement_fabric_order")
|
||||
metered = {s["fabric"] for s in sh}
|
||||
ran = {s["fabric"] for s in st}
|
||||
need(ran <= metered, 4, "measurement_fabric_unmetered")
|
||||
need(metered <= ran, 4, "measurement_fabric_unused")
|
||||
need(sum(s["joules_micro"] for s in sh) == joules, 4, "measurement_fabric_sum")
|
||||
|
||||
|
||||
def class_rules(c):
|
||||
"""§3.1 step 4 — §2.4.5-§2.4.7 and §2.6."""
|
||||
cls, b = c["class"], c["body"]
|
||||
|
|
@ -1435,6 +1484,8 @@ def class_rules(c):
|
|||
4, "measurement_no_power_reader")
|
||||
if b["bracket"] is not None:
|
||||
bracket_rules(b["bracket"], b["joules_micro"], b["energy_provenance"], b["energy_uncertainty"])
|
||||
if "fabrics" in b:
|
||||
fabric_rules(b["fabrics"], b["joules_micro"])
|
||||
elif cls == "operating-point":
|
||||
figure_rules(c)
|
||||
pts = [b[k] for k in ("op_requested", "op_committed", "op_delivered")]
|
||||
|
|
|
|||
|
|
@ -23534,6 +23534,164 @@
|
|||
"step": 4,
|
||||
"reason": "session_switch_invalid"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabrics_accept",
|
||||
"about": "a ModelBased figure of 9,000 uJ divided by fabric: the motion search ran on the CPU and on a GPU, the base's coding on the CPU, the object hashes on the GPU; the CPU's share is the task's own and the GPU's its whole rail, the work its sole user, and the shares sum to the figure",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"ade1d72401ae9890ddb985640c3939d4a7366a47ff8ec64dd9d3a1b197f160bdbf5e5d54a45d464b86d981762c8d8b70c189ba67d6f0fe0f0fa3498829949200\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"context": {
|
||||
"figure": {
|
||||
"claim": "{\"body\":{\"coverage\":\"Whole\",\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"grade\":\"derived\",\"joules_micro\":9000,\"method\":\"https://transaction.science/wai/extensions/energy-measurement\",\"oer_receipt\":null,\"work_unit\":\"subpixel\",\"work_units\":49152},\"class\":\"energy\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":0,\"sig\":\"81c6ff685f4bf15dc35e0ca55e9823d274185c5ac26299e3c803d90f51fc7b82f87151748d2ed4d755c04ece8a3b77b166f97ddf5e0810da11fd9d96663e1e06\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"5bb20a10b980be8c6d95915394f6e89cd49c16d0f5e94cd87c0ec6ce37bcfc5e\",\"subject_type\":\"object\"}"
|
||||
}
|
||||
},
|
||||
"expected": {
|
||||
"outcome": "accept",
|
||||
"link_hash": "18e3adfd42cfe7e95a5cab676508039a3228201cc9b8dfab627a7a13b3acec59"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabrics_null",
|
||||
"about": "a fabric record written null: a report without one has no member, so it has one canonical form",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":null,\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"d08ee5e9f8b1725316150be5e8eb2c3aa44dd384abf55ce6cb23497cc8f24533a5a4c7b9b54b1df0d51de1308b1a931220f06f3679261c1595a73c19bddbbf05\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 2,
|
||||
"reason": "field_type"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_step_unknown_member",
|
||||
"about": "a step carrying its work count: a step is its term, fabric and backend",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\",\"units\":48},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"bf3b79d56a0f330542dd44835a796c0c85de6d1ca93552c2a4602813fb0f3587628823095c8960a499c0c83378ad2656226153e79faa0a279014f27c1239350e\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 2,
|
||||
"reason": "unknown_member"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_backend_empty",
|
||||
"about": "a step whose backend is the empty string",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"8315dbdc55a7579bda8af4d6e6c74ecae852f1a1479d20b3b0062997be76032500cf3cbe78bd62fe901b5963177932a86afc74a9d921d956f681ef476705e60b\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 2,
|
||||
"reason": "field_type"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_share_not_integer",
|
||||
"about": "a share written as a string",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":\"6500\"},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"96617e85e614ab1af4b8973282d845bab99fcf5dc5a6c9842f588519b44da16ec805f9cb18c3e5c96634aed04389bde11cc8e961a51ad2e918b00d971183330e\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 2,
|
||||
"reason": "field_type"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_unknown",
|
||||
"about": "a share on a fabric energy-measurement 6.3 does not name",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"fpga\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"4b6524608520fccdf643c5cb3dedd95d9a9458cfddf5db5a4aba9633914b21e1ddf6459ffac2430852dbe3dc7fec8fb6462a74315de2ff10401ac8cde1121a0c\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_unknown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_attribution_unknown",
|
||||
"about": "a share attributed by a method energy-measurement 6.3 does not name",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"estimate\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"b402e8284b7ddb0e8d9ce8a95efe11e7b149e77bbbbc952bfe0b1c4529df7e20a4a3ca99fba81950c12cf6aca951ea4612d31e7724d294ce43eea50a64a8d407\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_unknown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_time_share",
|
||||
"about": "a shared GPU rail divided by its users' GPU time: busy time does not apportion energy, so energy-measurement 6.3 names no such attribution",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"time-share\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"1c27953daed7fa881dee0608475b91b85a25dd63c84000e4042b7f99e56c43aa6aaa9deced4e8cc35e69d07288656146763fca5365c4eaab5f60cd4c91ba4d02\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_unknown"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_steps_out_of_order",
|
||||
"about": "the motion search listed before the base's coding: steps are in byte order of their terms",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"d3d12b97d54fa252120ad54a1e1799c4648beedf078dc37223093ac6cd3d666e1ad61b399e1be0fe8d042697583fa3ad8085f1e0842fe88fe8e4b4abb623a505\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_order"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_share_twice",
|
||||
"about": "two shares on the CPU: a fabric has one share",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"fbec2c55c3d7a36f0d3f2093e25c31c5a55bd9e19b3c6f8879aa8dc266e1092fdecb2489f2bafb89fc5abbdf6adf643fc5d59d367fff38d2dd0bfdb4eab86008\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_order"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_no_step",
|
||||
"about": "shares with no step: a record says where the work ran",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"b45a7c64df5a376493f0136630ef1080c55c7c966f02eb5410b390c09340812c862a90b7b12703fdb953e58ab665ca55341b1eeb87afc1d5bda68a65a6b9d80e\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_order"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_unmetered",
|
||||
"about": "a step on the GPU with a share on the CPU alone: a fabric that ran work has a share",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":9000}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"0296dfdd393715cf081ae36dd7e1046345e41489c38a50e44c51ba7fac0f7456d03b5fd8508372517df2bfe583f813394f2f7fd8edaa9d6b68c3daf24a1c1001\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_unmetered"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_unused",
|
||||
"about": "a share on the GPU while every step ran on the CPU",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2500}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"a538aac1de0c7d1c8b50b141ae9e4d8a272264451e15e4736e62126d33f50c3f69e577e33a1b39a07862412718cb7f5f1a009bc38ed96f3297540db4a8bd270e\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_unused"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "measurement_fabric_sum",
|
||||
"about": "shares of 6,500 and 2,499 uJ under a figure of 9,000: the shares divide the figure",
|
||||
"procedure": "claim",
|
||||
"receipt": "{\"body\":{\"bracket\":null,\"build\":{\"profile\":\"release\",\"target\":\"x86_64-unknown-linux-gnu\",\"toolchain\":\"rustc 1.98.0 (example)\"},\"energy_calibration_ref\":null,\"energy_counter_filtering\":null,\"energy_provenance\":\"ModelBased\",\"energy_uncertainty\":null,\"fabrics\":{\"shares\":[{\"attribution\":\"task\",\"fabric\":\"cpu\",\"joules_micro\":6500},{\"attribution\":\"rail\",\"fabric\":\"gpu\",\"joules_micro\":2499}],\"steps\":[{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-code\"},{\"backend\":\"claim-conformance: a cpu backend\",\"fabric\":\"cpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"base-search\"},{\"backend\":\"claim-conformance: a gpu backend\",\"fabric\":\"gpu\",\"step\":\"object-hash\"}]},\"figure_index\":null,\"figure_profile\":\"moq-jwp:claim-link\",\"group\":null,\"joules_micro\":9000,\"power_reader\":\"reader:example\"},\"class\":\"measurement\",\"issued_at\":\"2026-09-27T12:00:00Z\",\"kind\":\"claim\",\"parent_receipt_hash\":null,\"seq\":1,\"sig\":\"7528210f3893e4066c79e478bd771fb9dff22ead676a08e86b76a89a0cb37b1fc041d1391250cdca436ad14f1e4a4efa22937c2c1a0f5eb480dea1f954cee201\",\"signer_id\":\"did:example:meter\",\"signer_pubkey\":\"a09aa5f47a6759802ff955f8dc2d2a14a5c99d23be97f864127ff9383455a4f0\",\"subject\":\"a2316cae02860ddb6643af9b2213dcc4768867fbcd46e32e17ad7aabccf8758e\",\"subject_type\":\"receipt\"}",
|
||||
"expected": {
|
||||
"outcome": "reject",
|
||||
"step": 4,
|
||||
"reason": "measurement_fabric_sum"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@
|
|||
> to decode, the page **routes** the object's capability to the user agent's decoder, a native
|
||||
> host's or WAI's own WebAssembly decoder (§4): the user agent is asked only about codec strings
|
||||
> the WebCodecs Codec Registry lists, and its decoder is preferred where it is exact for the
|
||||
> tier the object needs. The page evaluates the constraint sets of
|
||||
> tier the object needs. Among objects that are alternatives of one content, the page decodes
|
||||
> the one SPEC §4's energy order puts first, inside the routing: a decode routed to the user agent
|
||||
> that it answers supported, smooth and power-efficient before a software one, and a learned one
|
||||
> without a sink-metered figure of its decode last (§4.7). The page evaluates the constraint sets of
|
||||
> [capability-constraints](capability-constraints.md) itself (§5), and reports the joules a
|
||||
> decode was measured to cost beside the user agent's `powerEfficient` boolean, which stays the
|
||||
> user agent's word (§6). Every frame the page decodes carries a **content binding** (§7): the
|
||||
|
|
@ -38,7 +41,8 @@
|
|||
|
||||
## 1. Scope
|
||||
|
||||
In scope: the capability report a page builds (§3); routing an object to a decoder (§4);
|
||||
In scope: the capability report a page builds (§3); routing an object to a decoder (§4), and
|
||||
choosing among alternatives (§4.7);
|
||||
constraint sets evaluated in the page (§5); the energy entry beside `powerEfficient` (§6); the
|
||||
content binding, its form and when a page writes one (§7); what happens to it through a frame's
|
||||
life (§8); how a page carries it (§9); the state of the frame on screen (§10); and the reasons
|
||||
|
|
@ -52,12 +56,15 @@ with a state.
|
|||
Relationship to the core spec and the other extensions:
|
||||
|
||||
- **SPEC §4.** A page answers step 3's "the sink advertises `cap`" for an object with its route
|
||||
(§4): it advertises `cap` for that object when the route is not `none`.
|
||||
(§4): it advertises `cap` for that object when the route is not `none`. Among alternatives it
|
||||
applies SPEC §4's energy order (§4.7).
|
||||
- **[http-delivery](http-delivery.md) §4.3 and §12.** A WAI element is never put to a user-agent
|
||||
interface (§4.3 there). The route gives the Sink Capability Descriptor's `via` (§4.6).
|
||||
- **[capability-constraints](capability-constraints.md).** §5 is its evaluation in a page, over
|
||||
the register of its §3.1.
|
||||
- **[energy-measurement](energy-measurement.md) §2.** The classes a figure carries (§6).
|
||||
- **[platform-decode](platform-decode.md) §2, §3.** The query a page asks the platform, read
|
||||
from the payload, and how the answers enter the report.
|
||||
- **[jwp-receipts](jwp-receipts.md) §2.1 and §3 step 3.** The content hash a binding names, and
|
||||
the link hash of the group receipt.
|
||||
- **[determinism-tiers](determinism-tiers.md).** The tier vocabulary a need and a binding use.
|
||||
|
|
@ -118,7 +125,7 @@ report**: one JSON object.
|
|||
"calibration_ref": "<text>",
|
||||
"coverage": "Whole" | "Partial", "fabrics": [ "cpu" ] } },
|
||||
"objects": [ { "name": "<text>", "capability": "<capability>", "codec": "<codec string>" | null,
|
||||
"need": "<tier>", "params": { "<parameter>": 0 } } ] }
|
||||
"need": "<tier>", "params": { "<parameter>": 0 }, "choice": "<text>" } ] }
|
||||
```
|
||||
|
||||
- `browser_sink` is `1`.
|
||||
|
|
@ -134,11 +141,14 @@ report**: one JSON object.
|
|||
- `energy` holds measurements, by capability and route (§6).
|
||||
- `objects` lists what the page is about to decode, each object (or each track, whose objects
|
||||
share these values) with a `name`, its `capability`, the codec string a UA decoder would be
|
||||
asked under (`null` where none applies), its need, and its declared parameters (§5).
|
||||
asked under (`null` where none applies), its need, and its declared parameters (§5); and,
|
||||
optionally, a `choice`, a non-empty string that objects which are alternatives of one content
|
||||
share (§4.7).
|
||||
|
||||
A report that does not read in this shape, or whose `browser_sink` is not `1`, is refused whole
|
||||
(`report_malformed`): every member above is required, `objects` may be empty, and a member this
|
||||
text does not define is ignored. A codec string with an answer in `ua` that §4.2's gate refuses is
|
||||
(`report_malformed`): every member above is required but an object's `choice`, which, when
|
||||
present, is a non-empty string; `objects` may be empty, and a member this text does not define is
|
||||
ignored. A codec string with an answer in `ua` that §4.2's gate refuses is
|
||||
never read (§4.2).
|
||||
|
||||
## 4. Routing
|
||||
|
|
@ -280,6 +290,44 @@ answered `supported` (http-delivery §12.2). A capability routed `ua`, `wasm` or
|
|||
http-delivery §12.1's descriptor, `via` `"ua"` (with the codec strings asked and their answers,
|
||||
`provisional` true), `"wasm"` or `"host"`; one routed `none` is absent from it.
|
||||
|
||||
### 4.7 Choosing among alternatives
|
||||
|
||||
Objects of a report that carry the same `choice` are **alternatives**: renditions of one content
|
||||
of which the page decodes one, such as the entries of a `WAI2` envelope, a `WAI1` envelope's
|
||||
capability and fallback, or the renditions of one track. For each `choice`, in order of its first
|
||||
appearance in `objects`, the page puts the alternatives in SPEC §4's energy order. The order sits
|
||||
inside the routing: each alternative's route is the one §4.3 gives it, and the order changes none.
|
||||
|
||||
- An alternative routed `ua` ranks by the user agent's answer for its codec string
|
||||
([platform-decode](platform-decode.md) §3): `first` when `supported`, `smooth` and
|
||||
`power_efficient` are all `true`; `smooth` when `smooth` is `true` and `power_efficient` is
|
||||
not; `classical` otherwise. A `false` or a `null` is not a `true`: no answer is not a
|
||||
power-efficient one.
|
||||
- An alternative routed `host` or `wasm` ranks `classical`. The user agent's answers are about
|
||||
its own decoder, which does not run this object, so they are not read for it, whatever they
|
||||
say: AV1, whose user-agent tier is `none`, and a codec whose need is `decode-equivalence` while
|
||||
no probe ran (§4.4) route elsewhere and rank on nothing the user agent said.
|
||||
- An alternative whose capability is learned (SPEC §4: its replicate class is not one of the
|
||||
three standard classes, or the string is not registered) ranks `classical` when the report
|
||||
holds a measurement under its route's key (§6, `"<capability> <route>"`) that reads under §6
|
||||
with the class `OnChipCounter` or `CalibratedInstrument` and the coverage `Whole`: a
|
||||
sink-metered figure of its decode, with its class, uncertainty and scope. It ranks
|
||||
`learned-without-budget` otherwise. The figure's value is not read.
|
||||
- An alternative routed `none` comes after every other, with no rank.
|
||||
|
||||
Report order within a rank. `powerEfficient` is the user agent's hint, used as the user agent
|
||||
gave it, as an order and never as an energy figure or as evidence of hardware. The **chosen**
|
||||
alternative is the first in that order whose route is not `none`, and where every route is `none`
|
||||
nothing is chosen (`no_alternative`). A page **MUST** decode the chosen alternative of a choice
|
||||
and no other, so a learned alternative without a qualifying measurement is decoded only where no
|
||||
classical alternative can be routed. For a `WAI1` envelope this is SPEC §4's order of steps 3 and
|
||||
5: the chosen alternative is tried first, and the other after it if the first does not decode.
|
||||
|
||||
The **choice table** has one entry a choice: `choice`; `order`, the alternatives' names in that
|
||||
order, those routed `none` included; `ranks`, each one's rank in the same order, `null` for one
|
||||
routed `none`; `chosen`, a name or `null`; and `reason`, `null` or `no_alternative`. An object with
|
||||
no `choice` is in no entry, and a report whose objects carry none has an empty table.
|
||||
|
||||
## 5. Constraint sets in the page
|
||||
|
||||
A page evaluates [capability-constraints](capability-constraints.md) §2 in this form:
|
||||
|
|
@ -354,7 +402,8 @@ A figure that reads is reported with every member it was given and
|
|||
`per_decode_micro = ⌊joules_micro / decodes⌋`; `joules_micro` and `decodes` stay beside it, so the
|
||||
floor loses nothing. An object with no measurement is **unmetered**: `figure` and `reason` are
|
||||
both `null`, and a page presents it as unmetered, never as 0 J. In this revision a figure never
|
||||
changes a route; it is reported, not steered on. How a figure is measured, bracketed and
|
||||
changes a route, and none is steered on: §4.7 reads only whether a learned alternative's figure
|
||||
qualifies, never its value. How a figure is measured, bracketed and
|
||||
attributed is energy-measurement §3 and §4's, and a `Partial` coverage says the meter saw only
|
||||
the fabrics it names.
|
||||
|
||||
|
|
@ -518,6 +567,7 @@ display's colour space, which no tier covers.
|
|||
| `host_inexact` | §4.3 step 5 |
|
||||
| `no_wasm` | §4.3 step 6 |
|
||||
| `no_decoder` | §4.3 step 7 |
|
||||
| `no_alternative` | §4.7 |
|
||||
| `constraint_malformed` | §5 step 2 |
|
||||
| `parameter_unregistered` | §5 step 3 |
|
||||
| `energy_class_invalid` | §6 |
|
||||
|
|
@ -542,6 +592,16 @@ written by `wai_browser_sink_vectors gen`. It has:
|
|||
`supported` and never read, a need of `decode-equivalence` refused a UA decoder whose probe did
|
||||
not run and taken by one whose probe matched, the host and wasm fallbacks, and a malformed
|
||||
report.
|
||||
- **Choices**: reports whose objects are alternatives, each with its routing table and the choice
|
||||
table §4.7 gives it: `first`, `smooth` and `classical` before `learned-without-budget`; a
|
||||
power-efficient answer that is not smooth below a smooth one; a power-efficient answer for a
|
||||
codec routed to a host decoder (its need above the user agent's tier) read for nothing; a
|
||||
learned alternative with a qualifying measurement (`OnChipCounter`, `CalibratedInstrument`)
|
||||
ranked `classical`, and with a `Partial` or a `ModelBased` one ranked last; a learned
|
||||
alternative chosen where no other routes; no answer read as not power-efficient; two choices
|
||||
beside an object in none; a choice with nothing to choose (`no_alternative`); and an empty and a
|
||||
non-string `choice`, each refused whole. Every measurement in them is synthetic, marked
|
||||
`"note": "synthetic: no measurement"`.
|
||||
- **Constraints**: constraint sets and declared parameters, with §5's outcome: satisfied by one
|
||||
set of several, by an empty set, by `enum`, `minimum` and `maximum`; unsatisfied by an
|
||||
undeclared parameter and by a value of the wrong type; each malformed form and an unregistered
|
||||
|
|
@ -564,8 +624,8 @@ regenerates byte for byte, that `wai-rs` gives every case its stated outcome, th
|
|||
it shares no code with `wai-rs`, reads each capability's kind, status and replicate class from the
|
||||
registry export ([`registry/capabilities.json`](../registry/capabilities.json)), checks
|
||||
signatures with WAI's strict rule and hashes with BLAKE3 from `claim-conformance/`, and mutates
|
||||
copies of the corpus and requires every copy to fail. The page runs the route, constraint, energy,
|
||||
binding and frame cases through its wasm build, and its side table through the sequences, in a
|
||||
copies of the corpus and requires every copy to fail. The page runs the route, choice, constraint,
|
||||
energy, binding and frame cases through its wasm build, and its side table through the sequences, in a
|
||||
browser (`joule-moq-web`'s end-to-end scenario `binding`).
|
||||
|
||||
## 13. Security and privacy considerations
|
||||
|
|
|
|||
|
|
@ -531,31 +531,42 @@ shows, by fabric:
|
|||
(integer-operators §7.3). The host makes the interpreter's run-time checks before the
|
||||
device runs anything, and a `check` tensor that leaves its range refuses the decode at
|
||||
its operator. The driver's stage operators stay on the CPU, and so does a graph with an
|
||||
operator outside `wai-ops/1-i32`. The tier is met for those graphs on each adapter and
|
||||
backend below, where a run reproduced, at both widths, every graph vector of
|
||||
integer-operators §14.1 and §14.2 that `wai-ops/1-i32` covers (§6):
|
||||
operator outside `wai-ops/1-i32`. The tier is claimed for those graphs only under the
|
||||
conditions of *Claiming the tier on a GPU* (below), which the crate meets, and only on
|
||||
an adapter where a run, on the kernel text of this revision, gave WGSL's result for
|
||||
every guarded case of the integer semantics suite, the host's result for every word of
|
||||
the arithmetic probe, and the interpreter's outputs, at both widths, for every graph
|
||||
vector of integer-operators §14.1 and §14.2 that `wai-ops/1-i32` covers (§6). That is
|
||||
each adapter and backend below:
|
||||
- Metal, on the integrated GPU of an arm64 desktop (macOS 27.0.1);
|
||||
- a browser's WebGPU on that same GPU, the kernels compiled by the browser's own WGSL
|
||||
compiler (Chromium 148 and a Chromium-based browser at 155, headless);
|
||||
- Vulkan, on a discrete GPU of PCI vendor 0x10de, device 0x28e1 (driver 595.84), and
|
||||
on an integrated GPU of PCI vendor 0x8086, device 0xa7a0 (Mesa 25.2.8), in one Linux
|
||||
x86_64 host;
|
||||
- Vulkan, on an integrated GPU of PCI vendor 0x1002, device 0x1586 (Mesa 26.0.8),
|
||||
Linux x86_64.
|
||||
- Vulkan and DX12, on a discrete GPU of PCI vendor 0x10de, device 0x1f15 (Vulkan driver
|
||||
591.74, DX12 driver 32.0.15.9174), and on an integrated GPU of PCI vendor 0x8086,
|
||||
device 0x9a49 (Vulkan, and DX12 driver 31.0.101.3222), in one Windows x86_64 host. The
|
||||
DX12 backend translates the kernels to HLSL, compiled for shader model 5.1.
|
||||
|
||||
On each of these but the browser, the capability corpora (integer-operators §14.4)
|
||||
also decoded with their graphs on the device: every committed vector and refusal
|
||||
fixture of `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.audio.int_codec`,
|
||||
also decoded with their graphs on the device, as §6 records: every committed vector and
|
||||
refusal fixture of `wai.neural.int_hyper`, `wai.video.int_hyper`, `wai.audio.int_codec`,
|
||||
`wai.splat.int_codec`, `wai.neural.int_synth` and `wai.neural.int_mlicpp` gave the
|
||||
bytes, or the refusal code, of the decode on the CPU, and every graph the device ran
|
||||
gave the interpreter's outputs. A software Vulkan driver (llvmpipe, on both Linux hosts)
|
||||
gave the same results; it runs on the CPU. Nothing is claimed for an adapter, a backend
|
||||
or a driver version that is not listed; DX12 was not run. `wai.image.jpegai`'s scale
|
||||
graphs (`hs_y`, `hs_uv`, at `acc32`) and hyper decoders and context stages (`rec_y`,
|
||||
`rec_uv`, at `acc64`) ran on each of these devices but the browser with the
|
||||
interpreter's outputs, so its `entropy-consistency` claim (§5) holds with its scale
|
||||
path there. Its synthesis graphs use operators outside `wai-ops/1-i32` and ran on the
|
||||
CPU.
|
||||
gave the interpreter's outputs. Software adapters, which run on the CPU, gave the same
|
||||
results: a software Vulkan driver (llvmpipe) on the Linux host, and the software DX12
|
||||
adapter (PCI vendor 0x1414) on the Windows host. An integrated GPU of PCI vendor 0x1002,
|
||||
device 0x1586 (Mesa 26.0.8, Linux x86_64) reproduced the graph vectors and the
|
||||
capability corpora on the kernel text before this revision, which differs from it only
|
||||
in the shift masks and the two clamps that *Claiming the tier on a GPU* requires; the
|
||||
semantics suite has not run there, and the tier is not claimed on it until a run of
|
||||
this revision's kernels passes there. Nothing is claimed for an adapter, a backend or
|
||||
a driver version that is not listed. `wai.image.jpegai`'s scale graphs (`hs_y`,
|
||||
`hs_uv`, at `acc32`) and hyper decoders and context stages (`rec_y`, `rec_uv`, at
|
||||
`acc64`) ran on each of these devices but the browser with the interpreter's outputs,
|
||||
so its `entropy-consistency` claim (§5) holds with its scale path there. Its synthesis
|
||||
graphs use operators outside `wai-ops/1-i32` and ran on the CPU.
|
||||
- **NPU.** No decoder of these capabilities runs on one in this revision, and the tier is
|
||||
not claimed there. A network evaluated in floating point on a GPU or NPU does not meet
|
||||
it (§1).
|
||||
|
|
@ -568,6 +579,105 @@ digest, a reconstruction or an energy figure to it. Content that the application
|
|||
decrypts itself, as in Lane B of SPEC §6.5, is decoded like any plaintext, and the
|
||||
tier holds for it as it does for plaintext.
|
||||
|
||||
### Claiming the tier on a GPU
|
||||
|
||||
`decode-equivalence` holds on a GPU only where the shader that runs a decoder's graphs,
|
||||
the translation of that shader into the backend's language, and the device all keep the
|
||||
integer arithmetic the capability's registration fixes. The texts read here are WGSL, W3C
|
||||
Candidate Recommendation Draft, 21 October 2026, and naga 30.0.1, the translator wgpu
|
||||
30.0.1 compiles WGSL with.
|
||||
|
||||
WGSL defines each integer operation at run time, the cases that are errors when the
|
||||
operands are known before the shader runs included (WGSL §8.8, §8.10, §17.5): addition,
|
||||
subtraction, multiplication and negation of a concrete integer type are modulo 2^32;
|
||||
`e1 / 0` is `e1`, and `e1 % 0` is 0; the most negative `i32` divided by −1 is itself, and
|
||||
its remainder 0; a shift amount is taken modulo 32; an integer `clamp(e, low, high)` is
|
||||
`min(max(e, low), high)`; a bit field's offset and count are clamped to the word. The
|
||||
languages a WGSL module is translated into leave some of these open: MSL and HLSL leave
|
||||
signed overflow undefined, and MSL, HLSL and SPIR-V leave a zero divisor, the most
|
||||
negative value divided by −1, a shift by the width or more, and a `clamp` whose bounds are
|
||||
out of order undefined or to the implementation. Floating point is open even in WGSL: an
|
||||
implementation may reassociate operations, and may fuse them where the result is at least
|
||||
as accurate (WGSL §15.7.5). A subgroup operation's result depends on its subgroup, whose
|
||||
size is a power of two from 4 to 128 that the device compiler chooses, whose invocations
|
||||
have no defined relationship to `local_invocation_index`, and of whose invocations only
|
||||
the active ones take part (WGSL §15.5, §15.6.3). An out-of-bounds access has several
|
||||
permitted outcomes (WGSL §6.5.7).
|
||||
|
||||
So a decoder **MAY** claim `decode-equivalence` for output whose graphs ran on a GPU only
|
||||
when every condition below holds for every shader module that ran them, on that adapter,
|
||||
in that process. Otherwise the output is the GPU's, and no tier is claimed for it.
|
||||
|
||||
1. **Integer only.** Every scalar type in the module, of every declaration and of every
|
||||
expression, is `bool`, `i32` or `u32`; every literal is of one of them; every
|
||||
conversion is between them. No floating-point type appears for any purpose, an index
|
||||
or a loop bound included. With no floating-point operation, no reassociation or fusion
|
||||
an implementation may make changes a result: integer addition and multiplication
|
||||
modulo 2^32 give the same value in any order.
|
||||
2. **No subgroup or quad operation, and no extension.** The module enables nothing
|
||||
(`enable`) and calls no subgroup or quad built-in.
|
||||
3. **No result depends on scheduling.** Each output word is written by one invocation and
|
||||
read by no other invocation of the same dispatch. An atomic operation is one whose
|
||||
final value does not depend on the order in which invocations reach it (`atomicOr`,
|
||||
`atomicAnd`, `atomicMin`, `atomicMax`, `atomicAdd`, `atomicSub`), and the value it
|
||||
returns is not used. No access is out of bounds.
|
||||
4. **The translation gives WGSL's results.** A translation into a language that leaves an
|
||||
integer result open produces WGSL's result for each operation the module uses: signed
|
||||
addition, subtraction, multiplication and negation evaluated in unsigned arithmetic
|
||||
and reinterpreted; division and remainder through a guard that substitutes the divisor
|
||||
1 where WGSL's result is the dividend, and remainder 0; the absolute value of the most
|
||||
negative value, itself. naga 30.0.1 does all of these in MSL and HLSL (its `naga_div`,
|
||||
`naga_mod`, `naga_neg` and `naga_abs`, and signed arithmetic through unsigned); in
|
||||
SPIR-V, whose integer addition, subtraction, multiplication and negation wrap, it
|
||||
guards division and remainder. In MSL and SPIR-V the division guards are written only
|
||||
while its integer-division checks are on; in HLSL always. A host
|
||||
**MUST NOT** turn a translator's guards off: in wgpu, a module created unchecked, or
|
||||
`ShaderRuntimeChecks::int_div_checks` false. Where a translator does not produce
|
||||
WGSL's result for an operation, the module **MUST NOT** reach that operation on the
|
||||
operands concerned. naga 30.0.1 passes a shift amount to MSL, HLSL and SPIR-V without
|
||||
reducing it modulo 32, and writes an integer `clamp` as MSL's `clamp`; so every shift
|
||||
amount in the module is a literal below 32 or masked below 32 (`e & 31u`), and every
|
||||
`clamp` has literal bounds in order, another bound being written
|
||||
`min(max(e, low), high)`.
|
||||
5. **The adapter passes the conformance suite first.** Before an adapter runs any graph of
|
||||
the decode, in the same process, it gives WGSL's result for every case of the integer
|
||||
semantics vectors (`wai/wgsl-conformance/integer.jsonl`) that the file marks guarded,
|
||||
and for further cases of each operation that the implementation draws, and the host's
|
||||
result for every word of the implementation's probe of the wider arithmetic it builds
|
||||
from 32-bit words. And the implementation has reproduced, on that adapter, backend and
|
||||
driver, every graph vector of integer-operators §14.1 and §14.2 its subset covers, at
|
||||
each width it runs (integer-operators §12, *Evidence for a fabric*). An adapter that
|
||||
differs on any guarded case or probe word runs no graph.
|
||||
6. **The claim names what ran.** A receipt or report of a GPU decode at this tier names
|
||||
the adapter (backend, PCI vendor and device, driver) and the translator and its
|
||||
release.
|
||||
|
||||
The semantics vectors cover every integer operation WGSL defines on `i32`, `u32` and
|
||||
two-component vectors of them, with the edge cases above: every pair of a set of values
|
||||
at and near the edges of both types for the arithmetic, division and remainder, every
|
||||
shift amount from 0 to 40 and the extremes past it, and bit fields at and past the word.
|
||||
They mark as unguarded the cases item 4 keeps a module from, a shift amount of 32 or more
|
||||
and `clamp` bounds out of order, whose result is the hardware's through naga 30.0.1. A run
|
||||
reports what each adapter gives on them; a difference there does not refuse the adapter,
|
||||
since no module at this tier reaches one. The file is written from WGSL's definitions by
|
||||
`wai_ops_gpu::semantics::reference` and checked against it natively, and against the same
|
||||
reference compiled to wasm32, under Node and in a browser page.
|
||||
|
||||
`wai/wai-ops-gpu` meets these conditions. `tests/wgsl_rules.rs` holds every shipped module
|
||||
to items 1–4 on naga's IR: the types and literals; a closed list of expressions and
|
||||
built-in functions; the one atomic operation, `atomicOr`, and its unused result; the
|
||||
bindings, with no workgroup or private memory and no barrier; the shift masks and `clamp`
|
||||
bounds; that every integer operation a kernel uses is one the semantics suite runs; and
|
||||
naga's guard helpers in each module's MSL and HLSL translations, which disappear from the
|
||||
MSL with its division checks turned off. It also holds the crate to the checked
|
||||
module-creation call, and shows each planted violation (a floating-point value, `f16`, a
|
||||
subgroup reduction, an `atomicAdd`, an atomic's result read, workgroup memory and a
|
||||
barrier, an unmasked shift, a `clamp` with run-time bounds) refused by the rule it
|
||||
breaks. Discovery runs item 5's suite and probe on each adapter before it runs a graph;
|
||||
`tests/semantics.rs` shows every adapter refused when the suite's remainder is written by
|
||||
hand, `a - (a / b) * b`, which gives `a` where WGSL gives 0. In a browser,
|
||||
`wai-ops-gpu/browser` runs the same suite through the browser's own WGSL compiler.
|
||||
|
||||
## 6. Evidence status
|
||||
|
||||
What the repository demonstrates, claim by claim, as of 2026-09-27 (main at
|
||||
|
|
@ -603,7 +713,7 @@ task list has no Windows task after that day. The Windows results below are hist
|
|||
| `wai.neural.int_hyper` and `wai.video.int_hyper` at full model scale (the pretrained `mbt2018_mean`, exported to integers): the Rust decode reproduces the export tool's integer reference byte for byte (synthesis, hyper-decoder, the whole conditional bitstream, an eight-frame clip), and costs at most 1 dB against the same model's float decode on one pinned photograph | `wai-sota-vectors.yml` regenerates the vectors on the runner from pinned inputs (the photograph's pixels and the checkpoint's bytes by SHA-256, the Python stack by version) and runs both tests with `WAI_REQUIRE_SOTA_VECTORS=1`, under which a missing vector fails instead of skipping. The job also fails on any skip banner in the test log, and runs `require_vectors_rule.rs`, which checks that without the switch a variable naming absent or incomplete inputs fails as well. Rust is checked against the export of the same run on the same machine: the vectors come from float computation, and some of them differed between machines and between runs, so none of their hashes is pinned and none is compared across machines. The workflow has run on the forge's Linux runner (x86_64 hardware) and passed on every run since 2026-09-28 | `wai-rs/tests/int_hyper_synth.rs`, `int_video_sota_keyframe.rs`, `require_vectors_rule.rs`; `.github/workflows/wai-sota-vectors.yml` |
|
||||
| `wai.audio.int_codec`, `wai.splat.int_codec` | committed vectors that never skip. Their frozen hashes are the output of an independent Python integer reference, computed when the vectors were exported (`tools/wai_int_audio_export.py`, `tools/wai_int_splat_export.py`); no Python runs in CI. The conformance workflow's fast-kernel steps enable `audio_int` and `splat_int` and, in debug and release builds, decode both vectors against those hashes under the reference kernels and every fast-kernel policy (`tests/int_kernels_vectors.rs`); that passed on the forge's Linux x86_64, Linux arm64 and macOS arm64 runners for the landing chain's head `7cfcecfa` (run 745, 2026-09-30). The same steps also name `tests/int_audio.rs` and `tests/int_splat.rs`, which check each vector against the same hash and against its own metadata (sample count; attribute count, tensor size and grid side); Cargo declares each with its feature as a required feature, so a step that names it without that feature fails instead of running nothing. This row records no forge run of those two | `wai-rs/tests/int_audio.rs`, `int_splat.rs`, `int_kernels_vectors.rs`; `.github/workflows/wai-conformance.yml` |
|
||||
| `wai.neural.int_mlicpp` on its committed vector (synthetic seeded weights; a 64×64 image, 8 latent channels in 4 slices): the decode reproduces the export tool's independent integer reference byte for byte — the RGB, and every scale bucket and symbol of the latent stream in coding order (512 symbols over all 16 buckets of the scale table, 47 of the symbols coded by the rANS escape) — under the reference kernels and under the fast kernels on 1, 2, 3, 5 and all available threads; one integer unit changed in each of four chosen parameters, one on each path that feeds a value (a hyper-decoder weight, an entropy-network weight, a mean-head bias, a synthesis weight), changes the image; a header whose shapes the model cannot produce, or on which a network would form a buffer over the cap, is refused before anything is allocated (`tests/int_mlicpp_preflight.rs`, which counts allocations); damaged payloads are refused or decode, and never panic in an overflow-checked build | measured 2026-09-29 with rustc 1.98.0 on the source of the change that added this row: macOS arm64 native (the wai-rs tests, with overflow checks on and off; the `byte-exact-conformance` golden, release) and wasm32-wasip2 under wasmtime 49.0.1 (the same tests and golden, release; on wasm the fast kernels run single-threaded as portable scalar code, and `usize` is 32-bit). All pinned hashes matched on both. Not measured: x86_64 — the development machine has no x86_64 translation layer installed, and an x86_64-apple-darwin build of the golden compiles but cannot execute there — nor Linux or Windows. The golden runs on every leg of the byte-exact matrix (`wai-conformance.yml`, `wai-byte-equality.yml`) and `tests/int_mlicpp.rs` on every leg of `wai-conformance.yml` in both profiles. Both passed on the forge's Linux x86_64, Linux arm64 and macOS arm64 runners for the landing chain's head `7cfcecfa` (run 745, 2026-09-30); there is no Windows leg. The dispatch through `decode_envelope_strict` (the prior pinning the one-file model bundle, the strict gate) runs in `wai-neural-gates.yml`. The weights are synthetic, so nothing here measures compression quality | `wai-rs/tests/int_mlicpp.rs`, `wai-rs/tests/int_mlicpp_preflight.rs`, `byte-exact-conformance/src/int_mlicpp_golden.rs`, `wai-rs/tests/neural_native_decode.rs`; `tools/wai_int_mlicpp_export.py` |
|
||||
| `wai-ops/1-i32` graphs on GPUs (`wai/wai-ops-gpu`, §5): each device's arithmetic agrees with 128-bit host arithmetic on every probe case; every graph vector of `ops-conformance/` that the subset covers gives the interpreter's outputs or refusal code, at `acc32` and at `acc64`; the integer decoders, with their graphs on the device, give the CPU decode's bytes or refusal codes on every committed vector and refusal fixture, and every graph the device runs gives the interpreter's outputs | measured 2026-10-20 and 2026-10-21 with rustc 1.98.0 and wgpu 30.0.1, release builds, on the source of the change that added this row, on the adapters §5 lists: Metal on macOS 27.0.1 (arm64); headless Chromium 148, and a Chromium-based browser at 155, on that GPU; Vulkan on Linux x86_64 with the 0x10de and 0x8086 adapters (driver 595.84, Mesa 25.2.8) and, on another host, with the 0x1002 adapter (Mesa 26.0.8); and llvmpipe on both Linux hosts. On each, the probe (`tests/arith.rs`) agreed on every word of 1,051,757 cases, and the corpus (`tests/corpus.rs`) ran 813 vectors (1,104 decodes) on the 32-bit kernels and 1,426 (1,921 decodes) on the 64-bit ones, 212 of the decodes refusals, with no difference; 209 graphs use an operator outside the subset and ran on the CPU, and 95 vectors are refused at load. Run on the 32-bit kernels, 330 of the 613 graphs whose certificate does not meet `acc32` gave other results, so the width a graph runs at is the certificate's. Eighteen edits of one kernel expression each (a rounding shift or its addend off by one, a tie rule, a clamp bound moved by one, a dropped carry or cross term, a sign rule, a post bias, an index rule; `tests/mutants.rs`) were each caught by the corpus on every adapter, and an edited product by the probe. `wai-rs/tests/ops_gpu.rs` decoded the committed vectors and refusal fixtures of the six capabilities §5 names, the world renderer's two vectors and `wai.image.jpegai`'s `jpegai/v1` codestream, comparing every graph the device ran with the interpreter as well as each decode's bytes; none differed. The browser leg ran the corpus, not the decoders. `wai-ops-gpu.yml` runs the Metal and browser legs on the `macos` runner, where an absent adapter fails; this row records no forge run of it yet. No other workflow runs a GPU leg of these tests | `wai-ops-gpu/tests/`, `wai-ops-gpu/browser/`, `wai-rs/tests/ops_gpu.rs`; `.github/workflows/wai-ops-gpu.yml` |
|
||||
| `wai-ops/1-i32` graphs on GPUs (`wai/wai-ops-gpu`, §5, *Claiming the tier on a GPU*): every shipped module meets items 1–4 on naga's IR; each device gives WGSL's result for every guarded case of the integer semantics suite and agrees with 128-bit host arithmetic on every probe case; every graph vector of `ops-conformance/` that the subset covers gives the interpreter's outputs or refusal code, at `acc32` and at `acc64`; the integer decoders, with their graphs on the device, give the CPU decode's bytes or refusal codes on every committed vector and refusal fixture, and every graph the device runs gives the interpreter's outputs | measured 2026-10-22 with rustc 1.98.0 and wgpu 30.0.1 (naga 30.0.1), release builds, on the source of the change that added *Claiming the tier on a GPU*, on the adapters §5 lists: Metal on macOS 27.0.1 (arm64); headless Chromium 148, and a Chromium-based browser at 155, on that GPU; Vulkan on Linux x86_64 with the 0x10de and 0x8086 adapters (driver 595.84, Mesa 25.2.8) and llvmpipe; Vulkan and DX12 on Windows x86_64 with the 0x10de and 0x8086 adapters, and DX12 with the software adapter (PCI vendor 0x1414). `tests/wgsl_rules.rs` passed, its nine planted violations each refused by the rule named. On each native adapter, the semantics suite (`tests/semantics.rs`) gave WGSL's result for every one of 254,464 cases, the 25,088 committed and 4,096 SplitMix64 cases of each operation: 231,690 guarded, and the 22,774 unguarded too (a shift amount of 32 or more, `clamp` bounds out of order), so on these adapters the hardware's result there was WGSL's; the browser legs ran 82,432 cases with none differing, and the committed file reproduced under the reference compiled to wasm32, in the page and under Node 26. The probe (`tests/arith.rs`) agreed on every word of 1,051,757 cases; the corpus (`tests/corpus.rs`) ran 813 vectors (1,104 decodes) on the 32-bit kernels and 1,426 (1,921 decodes) on the 64-bit ones, 212 of the decodes refusals, with no difference; 209 graphs use an operator outside the subset and ran on the CPU, and 95 vectors are refused at load. Run on the 32-bit kernels, the graphs whose certificate does not meet `acc32` gave other results on some of them, so the width a graph runs at is the certificate's. Eighteen edits of one kernel expression each (`tests/mutants.rs`) were each caught by the corpus on every adapter, an edited product by the probe, and a remainder written by hand by the semantics suite at discovery. `wai-rs/tests/ops_gpu.rs` decoded the committed vectors and refusal fixtures of the six capabilities §5 names, the world renderer's two vectors and `wai.image.jpegai`'s `jpegai/v1` codestream on Metal and on the Linux host's Vulkan adapters, comparing every graph the device ran with the interpreter as well as each decode's bytes; none differed; the same on the Windows host's Vulkan and DX12 adapters and on its software DX12 adapter. The browser legs ran the corpus and the semantics suite, not the decoders. Before this change the kernels differed only in the shift masks and the two clamps; on that text, the same tests but the semantics suite passed on 2026-10-20 and 2026-10-21 on Metal (macOS 27.0.1, arm64), the browser at 148 and 155, the Linux host's adapters, and an integrated GPU of PCI vendor 0x1002, device 0x1586 (Mesa 26.0.8), on which nothing has run since. `wai-ops-gpu.yml` runs the Metal and browser legs on the `macos` runner, where an absent adapter fails; this row records no forge run of it yet. No other workflow runs a GPU leg of these tests | `wai-ops-gpu/tests/`, `wai-ops-gpu/browser/`, `wai-rs/tests/ops_gpu.rs`, `wgsl-conformance/`; `.github/workflows/wai-ops-gpu.yml` |
|
||||
|
||||
So at `entropy-consistency`, WAI's JPEG AI symbol decode is demonstrated across
|
||||
architectures (x86_64 against arm64) once the distribution indices are given; the
|
||||
|
|
@ -623,9 +733,10 @@ arm64 and on wasm32-wasip2 (a 32-bit target), measured on the development machin
|
|||
it also passed on the byte-exact matrix's Linux x86_64, Linux arm64 and macOS arm64
|
||||
legs in run 745. On GPUs, the committed vectors of `int_hyper` (and its video keyframe),
|
||||
`int_codec` audio and splat, `int_synth` and `int_mlicpp` decode with their graphs on
|
||||
the device to the CPU decode's bytes on each adapter §5 lists, measured by hand on small
|
||||
vectors; the `wai-ops-gpu` workflow repeats it on one of them, with no forge run
|
||||
recorded yet.
|
||||
the device to the CPU decode's bytes on each adapter §5 lists but the browser, measured by
|
||||
hand on small vectors, after each adapter gave WGSL's integer semantics on every guarded
|
||||
case; the `wai-ops-gpu` workflow repeats it on one of them, with no forge run recorded
|
||||
yet.
|
||||
|
||||
For the JPEG AI exact decoder's pinned reconstruction (§5), identity across
|
||||
machines rests on construction, and on one comparison: a single codestream on the
|
||||
|
|
|
|||
|
|
@ -521,6 +521,42 @@ Receipts sealed before this revision carry `HwShunt`. Its meaning is fixed as
|
|||
declared uncertainty, a filtering state or a calibration reference is
|
||||
ambiguous and **MUST** be rejected.
|
||||
|
||||
**New profiles.** The acceptance above is for receipts that could have been
|
||||
sealed before the refinement, and it is closed. The profiles that keep it are
|
||||
the ones listed in §2 whose figure carries a class from the start and that a
|
||||
verifier cannot date: the energy binding and its C2PA companion assertion, the
|
||||
`wai.video.int_motion` receipt and the `wai.video.*` OTT receipts, and the
|
||||
`energy` claim; in each, an issued `HwShunt` keeps the meaning above. Every
|
||||
other profile already refuses it: an optional label (§2.8), the label of a JWP
|
||||
object's leaf or group, the `measurement` and `operating-point` claims, the
|
||||
per-hop record of [live-metering](live-metering.md) §3.6 and the measurement a
|
||||
page reports under [browser-sink](browser-sink.md) §6.
|
||||
|
||||
A receipt profile, record, claim, table or registration member that carries an
|
||||
acquisition class and is defined after this revision (a **new profile**)
|
||||
**MUST NOT** accept `HwShunt`, in any position, from any producer:
|
||||
|
||||
- its verifier refuses a figure classed `HwShunt` under that profile's reason
|
||||
for an invalid class, and **MUST NOT** read it as `OnChipCounter`,
|
||||
`CalibratedInstrument` or any weaker class;
|
||||
- a figure that reaches a new profile from a vocabulary with only the three
|
||||
names, as `HwShunt`, enters it only where its producer states the refined
|
||||
class and its evidence again; otherwise it is carried as the unmetered
|
||||
marker. Mapping a refined class out to the three-name vocabulary (§2) is
|
||||
unchanged;
|
||||
- a new profile states in its own refusals table which reason refuses
|
||||
`HwShunt`.
|
||||
|
||||
The reference implementation checks a new profile's class with
|
||||
`energy_class::check_new_profile`, which refuses `HwShunt`
|
||||
(`AcquisitionError::LegacyInNewProfile`), a class beside a zero figure, a
|
||||
refined class without its evidence and a declaration below the resolution
|
||||
floor of §2.3, for every profile defined since.
|
||||
|
||||
A package counter read through RAPL or the platform's system management unit
|
||||
is an on-chip counter (§2.2): a figure read from one is `OnChipCounter`, with its
|
||||
filtering state (§2.4), never `HwShunt`.
|
||||
|
||||
### 2.7 Signed encoding
|
||||
|
||||
Receipts that sign a binary payload encode the class as one tag byte, followed
|
||||
|
|
@ -672,8 +708,12 @@ What the reference implementation seals in these families:
|
|||
- Two producers seal a new non-zero figure without a label, and so fall
|
||||
short of the **SHOULD** above. `world_model::frame_receipt` is handed its
|
||||
caller's figures without their class; a caller that knows the class builds
|
||||
the steps itself with `ProvStep::with_energy_class`. The in-browser demos of
|
||||
`wai-web` seal illustrative figures, fixed in code or passed in by the page.
|
||||
the steps itself with `ProvStep::with_energy_class`. Two in-browser demos of
|
||||
`wai-web`, the phasor kernel and the quantum calibration and job demos, seal
|
||||
an illustrative figure the page passes in. The other in-browser quantum demos
|
||||
of `wai-web` seal the unmetered marker, since a page reads no counter, and the
|
||||
world demo seals the page's figure (wall time times a coefficient the page
|
||||
declares) labelled `Estimator`.
|
||||
- A generator that recreates a committed conformance corpus byte for byte
|
||||
seals that corpus's figures unlabelled; `wai_quantum_ops_vectors gen` seals
|
||||
non-zero ones. Every committed corpus predates the label, so these
|
||||
|
|
@ -684,8 +724,8 @@ What the reference implementation seals in these families:
|
|||
|
||||
### 2.9 IOReport, and where this departs from other documents
|
||||
|
||||
IOReport — the SoC energy interface the reference meters read through a
|
||||
non-privileged reader — is classed `OnChipCounter`. Its readings are the
|
||||
A channel of IOReport, the SoC energy interface, is classed `OnChipCounter`
|
||||
when the producer reads it itself and shows it live (§3.1). Its readings are the
|
||||
measured silicon's own report of its energy, and this extension classes a
|
||||
figure by that provenance. The interface's channels sit in a group named
|
||||
"Energy Model", and it is a private interface whose derivation is not publicly
|
||||
|
|
@ -694,6 +734,15 @@ documented. That makes the counter's systematic error unstated, which an
|
|||
figure a model this implementation computed (`ModelBased`) or a constant
|
||||
(`Estimator`).
|
||||
|
||||
The reference meters do not read such a channel. On their macOS hosts the
|
||||
group's processor channels stop updating for an unprivileged reader, and the
|
||||
processor power they sample is one a non-privileged reader reports from a
|
||||
report it selects itself. That is a reported power (§3.1): the meters seal it
|
||||
`ModelBased`, with the reader named in the measurement report, and never
|
||||
`OnChipCounter`. The group's `GPU Energy` channel does advance for an
|
||||
unprivileged reader; a profile that reads it itself can seal `OnChipCounter`
|
||||
from it under §3.1.
|
||||
|
||||
Other documents in this repository class IOReport differently. This extension
|
||||
does not change them; where a figure passes between the two, the weaker class
|
||||
applies (§2):
|
||||
|
|
@ -794,6 +843,23 @@ never of a live rail: its samples advanced once at most. Its standard deviation
|
|||
is zero, and under a hardware class a reader refuses a bracket whose `sd_uw` is
|
||||
0 whatever liveness facts it carries.
|
||||
|
||||
**The test is run on the counter.** The rule above shows a counter live only
|
||||
when the samples it is run on are the counter's: the producer reads the counter
|
||||
itself and each sample is the counter's own advance over its interval. A power
|
||||
that another program reports, computed from a channel the producer does not
|
||||
read, gives no such evidence. Its samples can move while every channel it could
|
||||
have come from is frozen, so a bracket over them that passes this rule shows the
|
||||
report moving, not a counter live. A figure from a reported power **MUST NOT**
|
||||
be sealed `OnChipCounter` or `CalibratedInstrument`, whatever its brackets show;
|
||||
it is `ModelBased` at best, and its measurement report names the reader and
|
||||
what its samples are as the method (§8; the report's `power_reader`). On the reference
|
||||
macOS hosts (macOS 27, arm64) the processor channels of IOReport's
|
||||
`Energy Model` group read zero at every interval for an unprivileged reader, and
|
||||
the non-privileged reader the reference meters sample reports a processor power
|
||||
that keeps moving, taken from another report it identifies itself (both
|
||||
measured on two such hosts on 2026-10-24). Those meters therefore seal
|
||||
`ModelBased` (§2.9).
|
||||
|
||||
## 4. Attribution quality (REQUIRED)
|
||||
|
||||
The marginal method is only meaningful when the burst lifts power **clearly
|
||||
|
|
@ -923,7 +989,8 @@ Producers that fall short of this section, recorded rather than exempted:
|
|||
the generators that recreate a committed corpus with its unlabelled legacy
|
||||
figures (§2.8); `world_model::frame_receipt`, which is handed its caller's
|
||||
figures without their class or their bracket; and the in-browser demos of
|
||||
`wai-web`, which seal illustrative figures. A figure sealed with no class
|
||||
`wai-web` that seal a figure, illustrative (unlabelled) or estimated (labelled
|
||||
`Estimator`), with no measurement behind it. A figure sealed with no class
|
||||
cannot meet this section, because a `measurement` claim restates a class.
|
||||
|
||||
A figure published without these is not wrong so much as unusable: nothing about
|
||||
|
|
@ -1026,6 +1093,46 @@ and, under a hardware class, that the rail was live and the ratio is at least 1
|
|||
(jwp-receipts §2.4.8). It does not check the values, which cannot be re-measured
|
||||
(§5).
|
||||
|
||||
### 6.3 A figure divided by fabric
|
||||
|
||||
Work can run on more than one kind of processor, a **fabric**: `cpu`, `gpu`,
|
||||
`media` (a hardware video engine) or `neural` (a neural-network engine). A
|
||||
`measurement` report MAY carry a **fabric record** (jwp-receipts §2.4.8,
|
||||
`fabrics`): the steps of the work, each with every fabric that ran it and the
|
||||
backend that did, and the figure divided into one **share** per fabric that ran
|
||||
a step. The figure is the sum of its shares, and its class (§2) is the class of
|
||||
the whole: a share is no measurement of its own.
|
||||
|
||||
Each share states how its energy was attributed to the work:
|
||||
|
||||
- `task`: the operating system's account of the work's own energy on that
|
||||
fabric, as it accounts a task's use of the processors (on the reference
|
||||
hosts, the kernel's per-task CPU energy, `ri_energy_nj` of
|
||||
`proc_pid_rusage`, read by the process for itself), which other work on the
|
||||
host does not enter;
|
||||
- `rail`: the fabric's whole rail over the span, in which the work was the
|
||||
fabric's sole user, at least 99% of the busy time every user of the fabric
|
||||
accrued over it; a share that is exactly 0 because the work spent no time on
|
||||
the fabric is also `rail`.
|
||||
|
||||
No attribution divides a rail that other work shared by its users' busy time.
|
||||
Busy time does not apportion energy: a GPU kernel that keeps the GPU busy at a
|
||||
low clock and one that keeps it busy at a high clock accrue the same time and
|
||||
draw very differently, and a division by GPU time has been measured to charge
|
||||
a decode three to fifteen times the GPU energy it drew alone
|
||||
(`jouleclaw-energy`, `process_gpu`). Where a fabric that ran a step was shared
|
||||
and its rail cannot be attributed, the span has no fabric record: the producer
|
||||
meters it otherwise (§3's bracket, over the whole figure), or seals it
|
||||
unmetered. So does a span that ran a step on a fabric with no live counter: on
|
||||
the reference hosts (macOS 27 on arm64) the media engine and the neural engine
|
||||
have none an unprivileged process can read.
|
||||
|
||||
The record says where the work ran and what each fabric drew; it changes
|
||||
nothing a figure's verifier checks of the figure itself (§6.2). Which fabric
|
||||
ran a step changes no output of a step whose output its specification fixes
|
||||
([packaging](packaging.md) §11, its steps and fabrics), so a record is a statement about energy, not
|
||||
about what was produced.
|
||||
|
||||
## 7. Worked example
|
||||
|
||||
`wai.video.int_motion`, real-footage vector, 8 frames of 96×96 RGB, release
|
||||
|
|
@ -1044,10 +1151,12 @@ acquisition class : HwShunt
|
|||
```
|
||||
|
||||
The run above predates the refinement of §2.2–§2.6, and its receipt says
|
||||
`HwShunt`. The reference meter now emits `OnChipCounter` for the same interface,
|
||||
with filtering state `Unknown` (a non-privileged IOReport reader has no
|
||||
documented way to read a filtering state, if the interface has one) and the
|
||||
§2.3 uncertainty: the statistics of both brackets combined with the
|
||||
`HwShunt`. The reference meter now emits `ModelBased` for the same reader: its
|
||||
samples are a processor power the reader reports, not a counter the meter reads,
|
||||
and on the reference hosts the counters that power could have come from do not
|
||||
advance for an unprivileged reader (§3.1). A meter that reads a counter itself
|
||||
and shows it live emits `OnChipCounter`, with its filtering state and the §2.3
|
||||
uncertainty: the statistics of both brackets combined with the
|
||||
completion-count term and the `0.5/√3` µJ that rounding the figure to whole
|
||||
microjoules adds. It also opens its window from rest and divides by
|
||||
`C + N/2`; the run above opened on freshly started workers and divided by `C`,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# WAI Extension: Energy-Aware Content Steering
|
||||
|
||||
> Status: Draft. Registers the record `wai.live.steering`: a steering server's choice, for
|
||||
> one client, of the pathway that delivers a room's stream for the fewest joules within the
|
||||
> operator's quality and latency limits, decided from the per-hop session records
|
||||
> one client, of the pathway whose hops signed the fewest joules per unit of delivery within
|
||||
> the operator's quality and latency limits, decided from the per-hop session records
|
||||
> ([live-metering](live-metering.md) §3, `WMH1`) that each pathway's hops signed, and signed
|
||||
> so that anyone holding those records can recompute it. The operator signs a **steering
|
||||
> policy**, `WSP1`: the pathways, their hops, their limits and the window the evidence is
|
||||
|
|
@ -15,7 +15,10 @@
|
|||
> hop is unmetered, its classes differ or its evidence is stale, is never ranked above one
|
||||
> whose energy is, so that not measuring is never rewarded. The output is the steering
|
||||
> manifest that HLS and DASH players already read, written byte for byte, carrying the
|
||||
> decision's URI in one key that players ignore. No new transport. The reference
|
||||
> decision's URI in one key that players ignore. The figure is the hops' own energy
|
||||
> averaged over what they delivered, not the network's: a network's energy is largely flat
|
||||
> against the traffic it carries, so a pathway chosen by this rule spends what its hops
|
||||
> spend, and moving a client changes no link's energy (§5.5). No new transport. The reference
|
||||
> implementation is the `energy_steering` module of `wai-rs` (feature `energy_steering`);
|
||||
> the corpus is [`live-conformance/steering/`](../live-conformance/steering/), and its
|
||||
> second implementation is `live-conformance/energy_steering_verify.py`.
|
||||
|
|
@ -303,6 +306,26 @@ as 0 (§7.2). In particular:
|
|||
- **Stale evidence is not current evidence**, and a pathway whose hops delivered nothing
|
||||
(empty) has no rate to state.
|
||||
|
||||
### 5.5 What the figure covers
|
||||
|
||||
The figure covers what the pathway's hops signed: each hop's own energy over its record's
|
||||
span (live-metering §3.1), divided by what the edge hops delivered. It is an average over the
|
||||
window, an attribution of the hops' energy to their delivery, and not the energy one more
|
||||
client, or one byte fewer, would add or save:
|
||||
|
||||
- **The network between the hops is not in it.** No hop record carries the energy of the
|
||||
links, routers and access networks a pathway's bytes cross, and the energy of a wired
|
||||
network is largely flat against the traffic it carries. So a pathway with a lower figure
|
||||
is one whose hops spend less per unit they deliver; the rule makes no claim that moving a
|
||||
client, or sending it fewer bytes, lowers any network's energy.
|
||||
- **A hop's own energy is not all marginal either.** Much of a relay's draw is its host
|
||||
running at all; the record states the hop's energy over the span, and the figure divides
|
||||
all of it by the delivery, so the figure falls as a hop carries more.
|
||||
- **What a choice of pathway changes** is the hops that carry the client, and so whose
|
||||
energy and whose cost its delivery is attributed to. Where a deployment wants the energy
|
||||
a delivery changes at the client, it is the sink's decode path, its display and, on a
|
||||
mobile radio, the radio's draw, none of which this record carries.
|
||||
|
||||
## 6. The decision rule
|
||||
|
||||
The rule is a function of the policy; the evidence (§5); the client's report (§4); the
|
||||
|
|
@ -659,7 +682,7 @@ vector the same outcome, and requires every mutated copy of the corpus to fail.
|
|||
|
||||
The corpus's energy figures are synthetic, chosen to exercise the rule. It computes which
|
||||
pathway the rule places first, and claims nothing about the energy any deployment spends or
|
||||
saves.
|
||||
saves, on its hops or on any network (§5.5).
|
||||
|
||||
## 12. Security and privacy considerations
|
||||
|
||||
|
|
|
|||
|
|
@ -341,8 +341,9 @@ The procedure:
|
|||
[SPEC](../SPEC.md) §2's reader limits); apply §5.1; refuse a
|
||||
capability the variant's union does not list (`codecs_undeclared`).
|
||||
4. Select from the head exactly as [SPEC](../SPEC.md) §7.1 selects, deferring only
|
||||
the component-digest test to the bytes. A deployer policy MAY reorder and restrict
|
||||
the entries considered, as SPEC §7.1 allows. The result is the picked entry, the
|
||||
the component-digest test to the bytes, in SPEC §7.1's default order, SPEC §4's
|
||||
energy order. A deployer policy MAY reorder and restrict the entries considered,
|
||||
as SPEC §7.1 allows. The result is the picked entry, the
|
||||
companions SPEC §7.1 binds to it, and the refinement components whose
|
||||
`refines.base` is its `id`. SPEC §7.1 offers an envelope's refinements only when every
|
||||
one of its components conforms, which the verified head's manifest decides: where one
|
||||
|
|
@ -1304,6 +1305,13 @@ figure within it, the choice is option 0 and the budget is not met (`unmet`): a
|
|||
fetches the least it can, and reports that the budget was not held. A figure past the
|
||||
options names none.
|
||||
|
||||
What a budget governs is the figure the receipts state: the energy the producer spent
|
||||
making each option, already spent before the client asks. Choosing a lower option changes
|
||||
which figure is attributed to the client's playback; it saves none of that energy, and none
|
||||
of the network's, which is largely flat against the traffic it carries. What a smaller
|
||||
option can change is the client's cost in bytes, its decode work and, on a mobile radio,
|
||||
the radio's draw while it receives, none of which these figures carry.
|
||||
|
||||
**Entry order.** Within a `WAI2`, the selectable entries whose capability the sink has
|
||||
are grouped by capability in order of first appearance; a group's options are its
|
||||
entries by component length (then table index), each option's figure the entry's. The
|
||||
|
|
|
|||
|
|
@ -501,13 +501,12 @@ as `SessionReceipt::parent_receipt_hash` + `receipt_hash()` +
|
|||
the signed payload and reproducible on any sink, because it is a
|
||||
function of the world and its ops alone.
|
||||
- `joules_micro` — a **carried, measured** figure, never derived inside
|
||||
the receipt. Native sinks supply real energy via the sink-metering
|
||||
path, which needs **no elevated privileges**: on arm64 macOS the SoC
|
||||
energy counters are readable through IOReport (the source `macmon` /
|
||||
`asitop` use — no sudo), on x86 via RAPL, and per-process retired
|
||||
cycles/instructions via `proc_pid_rusage` everywhere. The reference
|
||||
the receipt. Native sinks supply energy via the sink-metering path,
|
||||
which needs **no elevated privileges**: on arm64 macOS a processor
|
||||
power a non-privileged reader reports, and the kernel's per-process
|
||||
energy (`proc_pid_rusage`); on x86 RAPL. The reference
|
||||
`wai_world_meter` brackets a replay burst against an idle baseline and
|
||||
reports the marginal measured CPU energy. A browser, lacking those
|
||||
reports the marginal CPU energy of the reported power. A browser, lacking those
|
||||
counters, supplies a wall-clock estimate against a **declared** power
|
||||
coefficient and labels it an estimate; an unmetered sink signs the
|
||||
unmetered marker `0`, which a reader presents as unmetered and never as a
|
||||
|
|
@ -517,9 +516,11 @@ as `SessionReceipt::parent_receipt_hash` + `receipt_hash()` +
|
|||
it as an optional signed label
|
||||
([energy-measurement](energy-measurement.md) §2.8): absent, the receipt
|
||||
keeps its legacy bytes and the figure is unlabelled; `wai_world_meter`
|
||||
labels what it measures `OnChipCounter`, with its declared uncertainty;
|
||||
by §2's definitions, a browser's wall-clock figure against a declared
|
||||
coefficient is `ModelBased`.
|
||||
labels its figure `ModelBased`, because its samples are a power another
|
||||
program reports, not a counter it reads and shows live
|
||||
([energy-measurement](energy-measurement.md) §3.1); by §2's definitions,
|
||||
a browser's wall-clock figure against a declared coefficient is
|
||||
`ModelBased` too.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -315,7 +315,11 @@ one such statement — a class-scoped `body` under a `class`
|
|||
discriminator — on one envelope.
|
||||
|
||||
Required members. A claim has no optional members: a nullable member
|
||||
is present and `null`, so one claim has one canonical form.
|
||||
is present and `null`, so one claim has one canonical form. There is one
|
||||
exception, a `measurement` claim's `fabrics` (§2.4.8): it is absent when the
|
||||
claim does not divide its figure by fabric and is never `null`, so a claim
|
||||
sealed before the member existed keeps its bytes and its link hash, and one
|
||||
claim still has one canonical form.
|
||||
|
||||
| field | type | meaning |
|
||||
|---|---|---|
|
||||
|
|
@ -678,6 +682,7 @@ receipt's identity for every other profile.
|
|||
| `bracket` | object or `null` | For a figure obtained under energy-measurement §3: `{ "idle": summary, "busy": summary, "window_us": integer, "completions": integer, "in_flight": integer, "opening": "from-rest" or "running" }`, a summary being `{ "n": integer, "mean_uw": integer, "sd_uw": integer, "live_intervals": integer, "longest_live_run": integer }` as energy-measurement §6.2 defines them. `null` otherwise. |
|
||||
| `build` | object | `{ "profile": "release" or "debug", "toolchain": string or null, "target": term or null }`: the build of the code measured (energy-measurement §5, §8). `release` is an optimised build (for a Rust build, `opt-level` 2, 3, `s` or `z`, whatever its profile is named); any other build is `debug`. A finer vocabulary is a new registration. |
|
||||
| `power_reader` | string or `null` | The reader of the power interface (energy-measurement §8). Required under a hardware class (rule 4). |
|
||||
| `fabrics` | object, or absent | Where the work the figure covers ran, and the figure divided by fabric (energy-measurement §6.3): `{ "steps": [step, ...], "shares": [share, ...] }`. A step is `{ "step": term, "fabric": term, "backend": string }`: a step of the work, in the vocabulary of the document that defines the figure's receipt (for `wai:video-package-receipt-id`, the steps of [packaging](packaging.md) §11), a fabric that ran it, and the implementation and device that did, a non-empty string. A share is `{ "fabric": term, "joules_micro": integer, "attribution": term }`. Absent, the claim does not divide its figure; never `null` (§2.4). |
|
||||
|
||||
A report carries none of what follows from its values: neither the §4 ratio nor
|
||||
its verdict, which every reader derives (energy-measurement §6.2). A bracket
|
||||
|
|
@ -723,6 +728,18 @@ Rules, checked at §3.1 step 4 in this order:
|
|||
**MUST** be the bracket's (`measurement_window_mismatch`), and the declared
|
||||
`relative_ppm` **MUST** be at least the least `p` the bracket supports
|
||||
(energy-measurement §6.2) (`measurement_declaration_below_bracket`).
|
||||
6. Where `fabrics` is present, in this order:
|
||||
- every fabric is `cpu`, `gpu`, `media` or `neural`, and every attribution `task`
|
||||
or `rail` (energy-measurement §6.3) (`measurement_fabric_unknown`);
|
||||
- there is at least one step and one share; the steps are in the order of their
|
||||
`step`, in byte order, and then of their fabric, in the order just given, each pair
|
||||
once; and the shares are in the order of their fabric, each fabric once
|
||||
(`measurement_fabric_order`);
|
||||
- every fabric a step names has a share (`measurement_fabric_unmetered`): a fabric
|
||||
that ran work is metered;
|
||||
- every share's fabric runs a step (`measurement_fabric_unused`);
|
||||
- the shares sum to `joules_micro` (`measurement_fabric_sum`): the record divides
|
||||
the figure, and the figure's class covers every share.
|
||||
|
||||
**Against the figure** (§3.1 step 6). The context is the receipt the claim
|
||||
names, verified under its own profile's rules; for `moq-jwp:object`, that is
|
||||
|
|
@ -1942,9 +1959,14 @@ order listed.
|
|||
2. **Body.** A `body` that is not an object is `field_type`. Otherwise
|
||||
it is checked against the class's member table (for a lawful-basis
|
||||
body, the table of the record that `record`, read first, names; see
|
||||
§2.4.6): a member absent is `missing_required_field`, an additional
|
||||
§2.4.6): a member absent is `missing_required_field` (but a
|
||||
`measurement` body's `fabrics`, which may be absent, §2.4), an additional
|
||||
member is `unknown_member`, and then, in the table's order, a member
|
||||
that is not of the type the table gives it is `field_type`. That is
|
||||
that is not of the type the table gives it is `field_type`; a `fabrics`
|
||||
that is `null` is `field_type`, and within it, as in a bracket, a member
|
||||
absent is `missing_required_field` and an additional one `unknown_member`
|
||||
(`steps` before `shares`, each array's rows read for their members before
|
||||
its rows' values). That is
|
||||
a wrong JSON type; an integer that is not one of §2.4.3; a byte
|
||||
string that is not lowercase hex, two digits a byte, or a hash that
|
||||
is not 64 of them; a value outside an enumeration this extension
|
||||
|
|
@ -2095,6 +2117,11 @@ and never folds it in: confirmation changes the outcome of no step here.
|
|||
| 4 | `measurement_figure_mismatch` | `joules_micro` is not the figure the bracket gives |
|
||||
| 4 | `measurement_window_mismatch` | under a hardware class, the declared window is not the bracket's |
|
||||
| 4 | `measurement_declaration_below_bracket` | under a hardware class, `relative_ppm` is below the least the bracket supports |
|
||||
| 4 | `measurement_fabric_unknown` | a fabric record names a fabric other than `cpu`, `gpu`, `media` and `neural`, or an attribution other than `task` and `rail` |
|
||||
| 4 | `measurement_fabric_order` | a fabric record with no step or no share, its steps not in the order of their step and fabric, or its shares not in the order of their fabric, or a pair or a fabric twice |
|
||||
| 4 | `measurement_fabric_unmetered` | a fabric record's step runs on a fabric the record gives no share |
|
||||
| 4 | `measurement_fabric_unused` | a fabric record's share is on a fabric no step of it runs on |
|
||||
| 4 | `measurement_fabric_sum` | a fabric record's shares do not sum to `joules_micro` |
|
||||
| 4 | `operating_point_empty_point` | a point is present with no member |
|
||||
| 4 | `operating_point_delivered_alone` | `op_delivered` is present without `op_committed` |
|
||||
| 4 | `operating_point_incomplete` | `op_requested` or `op_committed` is present without `op_delivered` |
|
||||
|
|
|
|||
|
|
@ -425,7 +425,10 @@ and every grant that was in effect for a present principal at some instant of th
|
|||
class, nothing when *B* is 0, and the remainder is attributed to no grant. An attributed
|
||||
share is an allocation of a hop's figure, not a measurement of the grant's use, and keeps
|
||||
the figure's class; it is **unmetered** for the grant when an unmetered record's entry for
|
||||
it moved bytes;
|
||||
it moved bytes. The allocation by bytes is an accounting convention: a hop's energy over
|
||||
its span is largely what running the hop costs, not a cost per byte, so a grant that moves
|
||||
fewer bytes is attributed a smaller share of the same figure and does not lower the hop's
|
||||
energy by that share, and nothing here states what the network between hops spent;
|
||||
- **funds**, where the tariff's `currency` is the grant's: ⌈*N* / 10^12⌉ minor units of it,
|
||||
with *N* = `publisher_us` × *p*<sub>pub</sub> + Σ<sub>*c*</sub> `subscriber_us`[*c*] ×
|
||||
*p*<sub>sub,*c*</sub> + `composition_us` × *p*<sub>comp</sub> + `recorded_us` ×
|
||||
|
|
@ -446,7 +449,9 @@ no tariff, past, within for the funds ceiling:
|
|||
|
||||
A joule ceiling is one number, and holding a use to it adds what each hop spent, whatever
|
||||
its class. The verdict is that comparison; the sum it compares is stated nowhere as a
|
||||
figure, and the attributed joules stay per class.
|
||||
figure, and the attributed joules stay per class. A joule ceiling is held against the
|
||||
attributed shares, so it caps what is accounted to a grant; it is not a statement that the
|
||||
grant's use changed by that much what the hops, or the network, spent.
|
||||
|
||||
A grant's **consumption entry** is 198 bytes: `grant` (32), `joules_ceiling` (8),
|
||||
`funds_ceiling` (8), `currency` (3), `publisher_us` (8), `subscriber_us` (9 × 8),
|
||||
|
|
@ -687,10 +692,22 @@ room its sessions carried, the objects and bytes in and out, the drops by reason
|
|||
receipts forwarded, its subscriber sessions' time by layer class from the subscriptions each
|
||||
held, the events it delivered, and a ledger from the room grants its tokens map to. It signs
|
||||
version 2 when it keeps a DVR store, with that store's figures (§3.9), and version 1
|
||||
otherwise. Its own energy is the unmetered marker, since no meter backs it. It connects to
|
||||
no other hop, so every session it serves that subscribes is a subscriber session. Its record
|
||||
recomputes from its summaries, which its own test checks, and the corpus holds a record it
|
||||
signed in that test (§10).
|
||||
otherwise. Its own energy is read from a counter the host keeps, sampled every 100 ms while
|
||||
the relay runs: on macOS the relay process's own CPU energy (`ri_energy_nj`, unprivileged;
|
||||
scope **task**: every thread of the relay and nothing else), and on a Linux host the
|
||||
operator states runs the relay alone, the CPU packages' RAPL energy (scope **package**). A
|
||||
record carries the counter's advance over its span as `OnChipCounter` (tag 4), filtering
|
||||
state `Unknown`, `window_us` the span, and a declared uncertainty covering the interpolation
|
||||
at each end of the span, the counter's lag at each end, for the package counter the noise
|
||||
its vendor states for a filtered counter at that window, and the rounding to whole
|
||||
microjoules; and only where the counter was live across the span (energy-measurement §3.1)
|
||||
and the relay served that room alone in it. Otherwise, and where the host offers no counter
|
||||
or the configuration turns it off, the record carries the unmetered marker. A session's own
|
||||
summary carries the unmetered marker always, since sessions run together in one process
|
||||
and the counter cannot tell their shares apart. It connects to no other hop, so every
|
||||
session it serves that subscribes is a subscriber session. Its record recomputes from its
|
||||
summaries, which its own test checks, and the corpus holds a record it signed in that test
|
||||
with its meter off (§10), so that its bytes do not depend on the host.
|
||||
|
||||
## 9. Reasons
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ of before it serves it (ai-usage §7).
|
|||
| `once` | 16 | a single-use nonce: when it is not zero, no two `join`s name grants with this `once` (§7.2); zero, the grant may be used again within its window |
|
||||
| `viewer` | 16 | the viewer id revocation names (§5); zero for none |
|
||||
| `policy` | 65 | a `WEP1` record (live-events §5): the event kinds it may send, its payload and text bounds, and its rate windows |
|
||||
| `joules_ceiling` | 8 | u64: the energy its use may spend, in microjoules; 2^64 − 1 for no ceiling |
|
||||
| `joules_ceiling` | 8 | u64: the energy its use may be attributed, in microjoules (live-metering §4.6 attributes a hop's figure by the bytes each grant moved, an accounting convention); 2^64 − 1 for no ceiling |
|
||||
| `funds_ceiling` | 8 | u64: the funds its use may spend, in minor units of `currency`; 2^64 − 1 for no ceiling |
|
||||
| `currency` | 3 | ISO 4217 alphabetic code; three zero bytes when there is no funds ceiling |
|
||||
| `profiles` | 1 + *n* | a u8 count, then the index in the descriptor of each publish profile it publishes with |
|
||||
|
|
|
|||
|
|
@ -4,11 +4,16 @@
|
|||
> this extension fixes what a verifier recomputes. This revision defines the policy,
|
||||
> its digest and what it may ask a packager to emit (§3), how a source is cut into
|
||||
> pieces (§4), the objects a packager writes, refinements and stills among them, and a
|
||||
> piece's ladder (§5), the stage index (§6), the floor (§7), what each class is offered
|
||||
> piece's ladder (§5), the stage index (§6), the floor, one or several, supplied as OBU
|
||||
> streams or as an existing ladder's CMAF files (§7), what each class is offered
|
||||
> over HTTP and MoQ and how its delivery is simulated (§8), the package receipt and live
|
||||
> windows (§9), and the package and its verification (§11). Reference impl: `wai-pack` —
|
||||
> windows (§9), the package, its verification and a re-pack from a parent (§11), and
|
||||
> where each step of a pack or a verify may run (§11.6).
|
||||
> Reference impl: `wai-pack` —
|
||||
> `policy`, `capcheck`, `source`, `scenecut`, `classify`, `plan`, `encode`, `png`,
|
||||
> `floor`, `cmaf`, `catalog`, `pack`, `live`, `delivery`, `verify`; in `wai-rs`, the
|
||||
> `floor`, `cmaf`, `cmaf_read`, `catalog`, `files`, `pack`, `live`, `delivery`, `verify`,
|
||||
> `fabric`, `fabric_meter`, `floor_decode`, and `wai-pack-gpu` for the steps a GPU runs;
|
||||
> in `wai-rs`, the
|
||||
> encoder `int_video_enc`, the refinement encoder `staged::wir1`, the stage index
|
||||
> `stage_index`, the receipt `package` and the delivery simulator `stage_measure::sim`;
|
||||
> the corpus [`pack-conformance/`](../pack-conformance/). Keywords **MUST**, **MUST NOT**,
|
||||
|
|
@ -32,8 +37,15 @@ where it names them.
|
|||
- **Class**: a delivery profile the policy names.
|
||||
- **Moment**: a declared span of presentation time with tags.
|
||||
- **Floor**: a rendition in a standard codec, for sinks that lack the primary.
|
||||
- **Floor name**: the name a floor rung's files and catalog entries carry (§7.1): its
|
||||
component's id when the component has one floor rung, the rung's id when it has
|
||||
several.
|
||||
- **Timebase** of a component: `num / den` units per second. For video one unit is
|
||||
one frame; for audio, one sample.
|
||||
- **Step**: one of the computations of a pack or a verify (§11.6), such as the base's
|
||||
motion search or an object's content hash.
|
||||
- **Fabric**: the kind of processor a step runs on: `cpu`, `gpu`, `media` (a hardware
|
||||
video engine) or `neural` (a neural-network engine) (§11.6).
|
||||
|
||||
## 3. Policy
|
||||
|
||||
|
|
@ -81,6 +93,7 @@ absent table reads as empty.
|
|||
| `component.media` | string | required | `video`, `image`, `audio`, `world`, `feed`, `avatar`, `film` |
|
||||
| `component.source` | string | absent | |
|
||||
| `component.timebase` | table | required | members `num`, `den`, each 1 to `2^32 − 1`, required |
|
||||
| `component.profile` | string | absent | `floor-only` (§7.8); absent, the standard profile |
|
||||
| `segmentation.target_ms` | integer | 2000 | |
|
||||
| `segmentation.min_ms` | integer | 1000 | |
|
||||
| `segmentation.max_ms` | integer | 4000 | |
|
||||
|
|
@ -99,6 +112,7 @@ absent table reads as empty.
|
|||
| `ladder.params` | table | empty | by capability, below |
|
||||
| `floor.supplied_listing` | string | absent | 64 lowercase hexadecimal digits |
|
||||
| `floor.extra_keyframes` | boolean | `false` | |
|
||||
| `floor.supplied_form` | string | absent | `cmaf` (§7.7); absent, the OBU form (§7.3) |
|
||||
| `live.layer_lag_ms` | array of integers | one 0 per layer | §3.4 |
|
||||
| `class` | array of tables | empty | |
|
||||
| `class.id` | string | required | 1 to 255 bytes |
|
||||
|
|
@ -157,15 +171,17 @@ first that fails is the refusal:
|
|||
chain of `refines` returns to a rung it passed (`policy-refines-cycle`); every
|
||||
chain stays in its component and ends at a base (`policy-refines-base`); no rung is
|
||||
refined twice (`policy-refines-branch`).
|
||||
8. Every component has a base rung (`policy-no-base`).
|
||||
8. Every component has a base rung (`policy-no-base`), but a floor-only one (§7.8), which
|
||||
has no base and no refinement rung, no `source`, and at least one floor rung
|
||||
(`policy-floor-only`).
|
||||
9. A rung's **layer** is 0 for a base or a floor, and one more than the rung it
|
||||
refines for a refinement; the policy has one more layer than its highest rung
|
||||
layer. No class's and no rule's `max_layer` exceeds the highest
|
||||
(`policy-class-layer`).
|
||||
10. A rule's `then.ladder` names known rungs, all of one component, each once:
|
||||
exactly one base, every refinement over a rung it names (so its layers are a chain
|
||||
from that base), and every floor rung of the component (every piece has a floor,
|
||||
§7.1) (`policy-rule-ladder`).
|
||||
exactly one base (none, for a floor-only component), every refinement over a rung it
|
||||
names (so its layers are a chain from that base), and every floor rung of the
|
||||
component (every piece has a floor, §7.1) (`policy-rule-ladder`).
|
||||
11. `live.layer_lag_ms` has one entry per layer, the first 0, the entries
|
||||
non-decreasing (`policy-layer-lag`).
|
||||
12. The deadline values (`defaults.deadline_ms` and every rule's `then.deadline_ms`)
|
||||
|
|
@ -205,8 +221,9 @@ no reference outside the piece, and no later object of the package references in
|
|||
it.
|
||||
|
||||
**4.2** Piece boundaries are a pure function of the source frames and the effective
|
||||
policy. Where the policy names `wai.pack.scenecut/1`, the boundaries **MUST** be those
|
||||
§4.3 computes. The list of piece starts, in timebase units, is the **plan**, and its
|
||||
policy; a floor-only component's, which has no source, of its first floor rung (§7.8).
|
||||
Where the policy names `wai.pack.scenecut/1`, the boundaries of a component with a
|
||||
source **MUST** be those §4.3 computes. The list of piece starts, in timebase units, is the **plan**, and its
|
||||
digest is
|
||||
|
||||
BLAKE3("wai:pack-plan\x01" ‖ u64 LE n ‖ n × u64 LE start)
|
||||
|
|
@ -475,7 +492,7 @@ bytes are zero.
|
|||
| 8 | 4 | `duration`, in timebase units |
|
||||
| 12 | 4 | `first_entry`, window-relative |
|
||||
| 16 | 2 | `n_entries` |
|
||||
| 18 | 1 | `kind`: 0 natural, 1 static, 2 synthetic, 3 motion_high (§4.6), 4 world segment, 5 audio |
|
||||
| 18 | 1 | `kind`: 0 natural, 1 static, 2 synthetic, 3 motion_high (§4.6), 4 world segment, 5 audio, 6 none (a floor-only component's piece, which no source classifies, §7.8) |
|
||||
| 19 | 1 | flags: bit 0 scene cut, bit 1 forced at `max` (§4.3), bit 2 started by a cue, bit 3 aligned to the lead (§4.5) |
|
||||
| 20 | 4 | `rules`: bit *i* set when rule *i* matched the piece (§8.1) |
|
||||
| 24 | 4 | `moments`: bit *i* set when the piece lies in moment *i* (§8.1) |
|
||||
|
|
@ -562,7 +579,9 @@ of these, checked in this order:
|
|||
(`layer-gap`); its tier (§6.4: `tier-entropy-passthrough`, `tier-weakest`, then
|
||||
`tier-mismatch`); a non-zero `prefix_digest` with a tier other than
|
||||
decode-equivalence (`digest-without-tier`); a deadline on an entry that is not a
|
||||
refinement (`deadline-role`); a floor without flag bit 2 (`floor-derived-from`); a
|
||||
refinement (`deadline-role`); a floor whose flag bit 2 is not set exactly when its
|
||||
piece has a base, so a floor presents its piece's base and a piece of floors alone
|
||||
presents none (`floor-derived-from`); a
|
||||
content hash an earlier entry has at another length (`dup-length`); the duplicate
|
||||
flag other than set exactly when an earlier entry has the content hash (`dup-flag`).
|
||||
3. Class by class: a flag with no meaning (`class-flags`); a plan value other than
|
||||
|
|
@ -615,12 +634,18 @@ and one without the source cannot. A floor's `sse` is `2^64 − 1`.
|
|||
**7.1** Every piece of a video, image or audio component **MUST** have a floor rendition
|
||||
in a capability of SPEC §5's mandatory floor or recommended set; a packager refuses a
|
||||
video component with no floor rung (`floor-missing`). A film or world package SHOULD
|
||||
have one. This revision packs a supplied AV1 floor (`wai.video.av1` or
|
||||
`wai.video.av1.lossless`, `source = "supplied"`).
|
||||
have one. A component may have several floor rungs: each piece then has one floor object
|
||||
per floor rung, in ladder order (§5.7), so one package carries a ladder of standard-codec
|
||||
renditions over one base, which is coded once. Each floor rung has a floor name (§2): the
|
||||
component's id when the component has one floor rung, the rung's id when it has several.
|
||||
A floor name names files (§7.3, §7.6, §8.3), so one that cannot be one path segment of a
|
||||
listing, or two floor names of a package equal once `A`–`Z` are mapped to `a`–`z`, are
|
||||
refused (`pack-id`, §11.1). This revision packs supplied AV1 floors (`wai.video.av1` or
|
||||
`wai.video.av1.lossless`, `source = "supplied"`), one stream per floor rung.
|
||||
|
||||
**7.2** *Floor objects.* Each piece's floor is one object of the floor rung's capability
|
||||
(§5.2), whose manifest carries `derived_from` (SPEC §7.1, *Derived renditions*) naming
|
||||
the piece's base: `motif` the base's content hash, `motif_capability` its capability,
|
||||
(§5.2), whose manifest carries `derived_from` (SPEC §7.1, *Derived renditions*), but in a
|
||||
floor-only component (§7.8), naming the piece's base: `motif` the base's content hash, `motif_capability` its capability,
|
||||
`renderer` the floor's maker, and `settings` `BLAKE3("wai:render-settings\x01" ‖
|
||||
JCS(settings))` (§3.6). For a supplied floor, `renderer` is `supplied@<the supplied
|
||||
listing's SHA-256>` and the settings are `{"source": "supplied", "supplied_listing":
|
||||
|
|
@ -633,13 +658,15 @@ sequence header's maximum frame height and width, the component's timebase `num`
|
|||
`den`), then each temporal unit as a `u32` little-endian length and its bytes as the
|
||||
stream holds them.
|
||||
|
||||
**7.3** *A supplied floor* is a directory holding, per video component, `<id>.obu`: an
|
||||
AV1 stream in the low-overhead OBU format (every OBU with its size field), one temporal
|
||||
unit per source frame. Its listing (SPEC §3.1) of every file is pinned by the policy's
|
||||
`floor.supplied_listing`. A packager **MUST** refuse, before it writes anything:
|
||||
**7.3** *A supplied floor* is a directory holding, per floor rung of a video component,
|
||||
`<floor name>.obu` (§7.1): an AV1 stream in the low-overhead OBU format (every OBU with
|
||||
its size field), one temporal unit per source frame. Its listing (SPEC §3.1) of every
|
||||
file is pinned by the policy's `floor.supplied_listing`. A packager **MUST** refuse,
|
||||
before it writes anything, the directory and then each floor rung's stream in ladder
|
||||
order:
|
||||
|
||||
- a directory whose listing does not hash to `floor.supplied_listing`
|
||||
(`floor-listing-mismatch`), and a component with no stream in it
|
||||
(`floor-listing-mismatch`), and a floor rung with no stream in it
|
||||
(`floor-stream-missing`);
|
||||
- a stream that does not split into temporal units each holding a frame, whose first
|
||||
unit holds no sequence header, with a sequence header whose bytes differ from the
|
||||
|
|
@ -652,6 +679,18 @@ unit per source frame. Its listing (SPEC §3.1) of every file is pinned by the p
|
|||
`KEY_FRAME` and `show_frame` 1, or the sequence uses the reduced still-picture header;
|
||||
a unit that shows an existing frame does not.
|
||||
|
||||
*Key-frame sequence headers (informative).* A floor taken from an existing ladder without
|
||||
re-encoding (its CMAF samples demuxed to this form, each with its temporal delimiter put
|
||||
back) needs a sequence header in every unit that starts at a shown key frame. Where a
|
||||
ladder's key-frame samples lack one, a producer that puts one back must put back the
|
||||
stream's own first sequence header OBU, byte for byte, after each such unit's temporal
|
||||
delimiter: §7.3 refuses any other bytes (`floor-av1-malformed`). The sequence header in
|
||||
the ladder's sample entry (`av1C`) is not always the stream's: an encoder may write it
|
||||
before it settles its coding tools, so that it differs from the in-band one in its tool
|
||||
flags, and a stream with that header put back is refused. The packager writes its own
|
||||
`av1C` from the in-band header (§7.6). A ladder whose key frames carry no in-band header
|
||||
cannot be taken in the CMAF form (§7.7), whose units are its samples as they are.
|
||||
|
||||
The plan a supplied floor is made against is the **keyframe plan**, the file
|
||||
`plan-<id>.txt`: each piece's first frame in decimal, one per line, each line ended by a
|
||||
line feed. Its pieces' starts are the plan of §4.2, and `wai-pack plan` writes it from the
|
||||
|
|
@ -663,10 +702,10 @@ no tier or `replicate` claim covers it.
|
|||
**7.5** A floor produced by an encoder that is not a pure function of its input (§11)
|
||||
**MUST** name that encoder and its version in `derived_from.renderer`.
|
||||
|
||||
**7.6** *CMAF.* The floor of a video component is also written as CMAF files, which no
|
||||
stage index lists: `floor/<id>/init.mp4`, and one fragment per piece,
|
||||
`floor/<id>/<SHA-256 of the fragment>.m4s`. Box layouts are ISO/IEC 14496-12's, with the
|
||||
AV1 binding's sample entry; every integer is big-endian and every time 0:
|
||||
**7.6** *CMAF.* Each floor rung's floor is also written as CMAF files, which no stage
|
||||
index lists: `floor/<floor name>/init.mp4`, and one fragment per piece,
|
||||
`floor/<floor name>/<SHA-256 of the fragment>.m4s`. Box layouts are ISO/IEC 14496-12's,
|
||||
with the AV1 binding's sample entry; every integer is big-endian and every time 0:
|
||||
|
||||
- `init.mp4`: `ftyp` (major brand `cmfc`, minor version 0, compatible `cmfc`, `iso6`,
|
||||
`av01`), then `moov` holding `mvhd` (version 0, timescale `tb_num`, rate 1, volume 1,
|
||||
|
|
@ -692,6 +731,100 @@ AV1 binding's sample entry; every integer is big-endian and every time 0:
|
|||
The codecs string of the floor is the AV1 binding's short form,
|
||||
`av01.<profile>.<level, two digits><M or H>.<bit depth, two digits>`.
|
||||
|
||||
**7.7** *A supplied CMAF floor.* With `floor.supplied_form = "cmaf"`, a supplied floor is
|
||||
an existing ladder's CMAF files rather than OBU streams: per floor rung of floor name *f*
|
||||
(§7.1), a directory `<f>/` holding `init.mp4` and the floor's media segments, which are
|
||||
every other file of `<f>/`, each named `*.m4s` and in no directory below it, in byte order
|
||||
of their names, at least one. The floor's temporal units are the segments' samples, in
|
||||
order, each a temporal unit without its temporal delimiter: a unit is the two bytes
|
||||
`0x12 0x00` (a temporal delimiter OBU with its size field and no extension) followed by the
|
||||
sample. Boxes are ISO/IEC 14496-12's; a box's 32-bit size is at least 8, a size of 1 is the
|
||||
64-bit size that follows its type (at least 16), and a size of 0 is refused; a box's
|
||||
children tile its body:
|
||||
|
||||
- `init.mp4`: its top-level boxes tile the file; one `moov`, holding one `trak` and one
|
||||
`mvex`; the `trak`'s `tkhd` names the track, its `mdia`'s `mdhd` gives the timescale
|
||||
*T*, its `hdlr` is of handler type `vide`, and its `stsd` holds one sample entry, of type
|
||||
`av01`; an `edts`, where the `trak` has one, holds an `elst` of one entry, at media
|
||||
time 0 and rate 1; and the `mvex` holds one `trex`, of the track, whose defaults a
|
||||
sample takes.
|
||||
- a media segment: its top-level boxes tile the file: boxes of the types `styp`, `sidx`,
|
||||
`prft`, `emsg`, `free` and `skip`, then one or more `moof`, each followed by an `mdat`,
|
||||
and nothing else. A `moof` holds an `mfhd` and one `traf`, and nothing else; the `traf`
|
||||
holds one `tfhd` of the track, one `tfdt` and one or more `trun`, and nothing else; the
|
||||
`tfhd`'s flags are among `0x2`, `0x8`, `0x10`, `0x20` and `0x20000` (no base data
|
||||
offset, no empty duration); each `trun`'s are among `0x1`, `0x4`, `0x100`, `0x200`,
|
||||
`0x400` and `0x800`, `0x1` (its data offset) among them. A sample's duration, size and
|
||||
flags are its `trun`'s (the first sample's flags its first-sample flags where the `trun`
|
||||
gives them), else the `tfhd`'s defaults, else the `trex`'s; its bytes start at the
|
||||
`moof`'s first byte plus its `trun`'s data offset, a run's samples one after another,
|
||||
and lie in the `mdat` after the `moof`.
|
||||
|
||||
A packager **MUST** refuse, before it writes anything, after the directory's listing
|
||||
(§7.3), floor rung by floor rung in ladder order and segment by segment in order:
|
||||
|
||||
1. a floor rung with no `<f>/init.mp4` (`floor-stream-missing`); another file of `<f>/`
|
||||
that is not a media segment, or no media segment (`floor-cmaf-malformed`);
|
||||
2. an `init.mp4` that is not the above (`floor-cmaf-malformed`), or whose timescale is 0
|
||||
or whose edit list is another (`floor-cmaf-timing`);
|
||||
3. a media segment that is not the above, or that holds a sample of no bytes
|
||||
(`floor-cmaf-malformed`), or a sample with a composition offset other than 0 or a
|
||||
`moof` whose decode time is not where the one before it ended (`floor-cmaf-timing`),
|
||||
each where it is read; then one whose first sample is not a sync sample
|
||||
(`sample_is_non_sync_sample` set) (`floor-cmaf-malformed`); then a sample that does
|
||||
not last one frame (`duration · num = T · den`), or a segment whose first decode time
|
||||
is not the frame where the segment before it ended, the first at frame 0
|
||||
(`dts · num = frame · T · den`) (`floor-cmaf-timing`); then its units, as §7.3 reads a
|
||||
stream's (`floor-av1-malformed`, a unit's own faults held to the end);
|
||||
4. then, as §7.3: a unit count other than the source's frames (`floor-frame-count`), and
|
||||
the faults held back (`floor-av1-malformed`);
|
||||
5. other than one media segment per piece of the plan, each holding exactly its piece's
|
||||
frames (`floor-cmaf-pieces`);
|
||||
6. a key frame §7.3 refuses (`floor-keyframe-mismatch`).
|
||||
|
||||
The floor's CMAF files are then the supplied ones, byte for byte: `floor/<f>/init.mp4`
|
||||
the supplied `init.mp4`, and per piece `floor/<f>/<SHA-256>.m4s` its media segment (§7.6's
|
||||
writer is not used), which §8.3's playlists and MPDs list. The package also carries the
|
||||
supplied floor's listing as `floor.listing`: its SHA-256 is `floor.supplied_listing`, which
|
||||
the policy digest, and so the receipt, binds, and it names each of the ladder's files by
|
||||
its SHA-256, so the receipt binds the ladder's bytes. A server may hold those files once,
|
||||
for the ladder and the package alike (by a hard link, or by their content address): they
|
||||
are the same bytes. The floor objects carry the units as §7.2 frames them, as in the OBU
|
||||
form.
|
||||
|
||||
**7.8** *The floor-only profile.* A component of `profile = "floor-only"` is its floor
|
||||
rungs alone: no base, no refinement and no source (§3.4 item 8). It is for a library that
|
||||
delivers its standard-codec renditions as WAI objects, each named by its content hash,
|
||||
listed in a signed stage index and bound to a signed receipt, without the integer lane.
|
||||
|
||||
- *Its pieces* are its first floor rung's: in the CMAF form (§7.7), one piece per media
|
||||
segment; in the OBU form (§7.3), one piece from each of the stream's shown key frames
|
||||
to the next, the stream's first unit one (`floor-keyframe-mismatch` otherwise), and the
|
||||
first floor rung's unit count is the component's frame count. A piece of more than
|
||||
`max_ms` (§4.4) is refused (`floor-piece-length`); `min_ms` and `target_ms` do not
|
||||
apply, and the scene-cut segmenter does not run. Every other floor rung is held to those
|
||||
pieces as §7.3 and §7.7 hold a floor to a plan. A piece's kind is `none` (§6.2), its
|
||||
flags 0; a rule that states `when.piece_kind` matches none of them, and its moments are
|
||||
§8.1's.
|
||||
- *Its objects*: each piece has one floor object per floor rung (§7.1) and nothing else.
|
||||
A floor object presents no base, so its manifest is §5.2's for its capability with no
|
||||
`derived_from`, and its entry's flag bit 2 is clear (§6.3); everything else is §7.2's.
|
||||
Every class lists the floor only (§8.1): no catalog lists a WAI object, the MoQ catalog
|
||||
is the floors' tracks, and the package has no WAI playlist or MPD.
|
||||
- *What it attests*: the receipt signs the policy, so the supplied floor's listing, and
|
||||
the stage index, so every floor object's content hash, length and capability; a
|
||||
verifier recomputes the dedup counts, holds every object to its hash and to §7.2's
|
||||
framing, its units to the AV1 stream's structure (temporal units, one per frame,
|
||||
sequence headers, shown key frames at the piece starts), and the CMAF files to the
|
||||
units (§7.6) or, in the CMAF form, to the ladder's bytes through `floor.listing`
|
||||
(§7.7). A sink that checks an object's hash against the index receives the bytes the
|
||||
signer packed, or refuses them.
|
||||
- *What it does not attest*: a floor states no tier (§7.4): no decoded-output digest is
|
||||
stated or checked, so nothing says what a decoder makes of the units; there is no
|
||||
source, so no `sse` and no fidelity to anything; there is no primary, so no
|
||||
`replicate` claim; and the pieces are not classified. It attests delivery, not
|
||||
pictures.
|
||||
|
||||
## 8. Classes, catalogs and delivery
|
||||
|
||||
**8.1** *What a class lists.* The stages listed for a class are a pure function of the
|
||||
|
|
@ -704,7 +837,8 @@ catalog bytes.
|
|||
`e_ms > from_ms`. Rule *i* matches it when each condition the rule states holds: its
|
||||
kind is in `when.piece_kind`, and a tag of a moment it lies in is in
|
||||
`when.moment_tag`. Matching rules fold in document order (§3.4).
|
||||
- A floor-only class lists the floor (`0xFF`). Otherwise the class lists
|
||||
- A floor-only class lists the floor (`0xFF`), and every class lists only the floor of a
|
||||
piece with no base, a floor-only component's (§7.8). Otherwise the class lists
|
||||
`min(max_layer, the rules' then.max_layer, the piece's highest layer)`; under
|
||||
`max_kbps`, while the listed prefix needs more than `max_kbps`, that is
|
||||
`ceil(8 · bytes · num / (1000 · duration · den))` kilobits per second over the bytes of
|
||||
|
|
@ -737,18 +871,20 @@ second; a variant's `BANDWIDTH` is the largest over the pieces it lists and
|
|||
`AVERAGE-BANDWIDTH` the rate of all their bytes over all their durations. A floor
|
||||
variant's bytes per piece are its fragment's; a WAI variant's are the listed bases' and
|
||||
refinements', over the pieces its HTTP view lists (above). Every playlist
|
||||
line ends with a line feed. For component *c*, of frame size `W×H`, whose floor has
|
||||
codecs string *F* and maximum frame size `FW×FH`:
|
||||
line ends with a line feed. For component *c*, of frame size `W×H`, and each of its
|
||||
floor rungs, in ladder order, of floor name *f* (§7.1), codecs string *F* and maximum
|
||||
frame size `FW×FH`:
|
||||
|
||||
- `hls/classic/master.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="F",RESOLUTION=FWxFH`,
|
||||
`../classic/floor-<c>.m3u8`: a URI that names the same playlist from every class's
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS`, then per floor rung
|
||||
`#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="F",RESOLUTION=FWxFH` and
|
||||
`../classic/floor-<f>.m3u8`: a URI that names the same playlist from every class's
|
||||
directory, so each class's master differs from this one only by the session-data
|
||||
line, as [http-delivery](http-delivery.md) §11.1 and §11.8 require.
|
||||
- `hls/classic/floor-<c>.m3u8`: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||||
- `hls/classic/floor-<f>.m3u8`, per floor rung: `#EXTM3U`, `#EXT-X-VERSION:7`,
|
||||
`#EXT-X-TARGETDURATION:<t>`, `#EXT-X-MEDIA-SEQUENCE:0`, `#EXT-X-PLAYLIST-TYPE:VOD`,
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-MAP:URI="../../floor/<c>/init.mp4"`, then per
|
||||
piece `#EXTINF:<S.mmm>,` and `../../floor/<c>/<sha256>.m4s`, then `#EXT-X-ENDLIST`.
|
||||
`#EXT-X-INDEPENDENT-SEGMENTS`, `#EXT-X-MAP:URI="../../floor/<f>/init.mp4"`, then per
|
||||
piece `#EXTINF:<S.mmm>,` and `../../floor/<f>/<sha256>.m4s`, then `#EXT-X-ENDLIST`.
|
||||
- `hls/<class>/master.m3u8`: the classical master, byte for byte, with, when the class
|
||||
lists a WAI object over HTTP, `#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"`
|
||||
after `#EXT-X-INDEPENDENT-SEGMENTS`, and no other change.
|
||||
|
|
@ -759,8 +895,9 @@ codecs string *F* and maximum frame size `FW×FH`:
|
|||
`#EXT-X-STREAM-INF:BANDWIDTH=<b>,AVERAGE-BANDWIDTH=<a>,CODECS="<union>",X-WAI-FLOOR="<floor>",RESOLUTION=WxH`,
|
||||
`wai-<c>.m3u8`. The union and the floor are [http-delivery](http-delivery.md) §4.2's
|
||||
over the objects the class lists.
|
||||
- `hls/<class>/wai-<c>.m3u8`: the floor playlist's lines without `#EXT-X-MAP`, adjacent
|
||||
to it ([http-delivery](http-delivery.md) §11.2); per piece, `#EXT-X-GAP` when the
|
||||
- `hls/<class>/wai-<c>.m3u8`: the first floor rung's playlist's lines without
|
||||
`#EXT-X-MAP`, adjacent to it ([http-delivery](http-delivery.md) §11.2) and so to every
|
||||
floor rung's, which list the same pieces; per piece, `#EXT-X-GAP` when the
|
||||
class's HTTP view lists nothing of it (a floor-only piece, or one whose base is not of
|
||||
the default base capability), `#EXTINF:<S.mmm>,`, each refinement the class lists,
|
||||
in entry order, as `#WAI-LAYER:N=<layer>,URI="../../objects/<hash>.wai#n=<byte_len>"`,
|
||||
|
|
@ -778,10 +915,13 @@ over the component's whole duration and its target duration, `<Period id="p0" st
|
|||
one `AdaptationSet`, `</Period>` and `</MPD>`, indented by two spaces a level:
|
||||
|
||||
- `dash/classic.mpd`: `<AdaptationSet id="1" contentType="video" mimeType="video/mp4" codecs="F" width="FW" height="FH" segmentAlignment="true" startWithSAP="1">`
|
||||
holding `<Representation id="<c>-floor" bandwidth="<b>">` with
|
||||
`<SegmentList timescale="<num>">`: `<Initialization sourceURL="../floor/<c>/init.mp4"/>`,
|
||||
holding, per floor rung, `<Representation id="<f>-floor" bandwidth="<b>">` with
|
||||
`<SegmentList timescale="<num>">`: `<Initialization sourceURL="../floor/<f>/init.mp4"/>`,
|
||||
a `SegmentTimeline` of one `<S t="<(start − first start)·den>" d="<d·den>"/>` per
|
||||
piece, and one `<SegmentURL media="../floor/<c>/<sha256>.m4s"/>` per piece.
|
||||
piece, and one `<SegmentURL media="../floor/<f>/<sha256>.m4s"/>` per piece. With
|
||||
several floor rungs, the `AdaptationSet` carries no `codecs`, `width` or `height`, and
|
||||
each `Representation` carries its own after `bandwidth`:
|
||||
`<Representation id="<f>-floor" bandwidth="<b>" codecs="F" width="FW" height="FH">`.
|
||||
- `dash/<class>.mpd`, when the class's HTTP view lists a WAI object:
|
||||
`<AdaptationSet id="1" contentType="video" mimeType="application/wai" codecs="<union>" width="W" height="H">`
|
||||
holding `<EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"/>`,
|
||||
|
|
@ -819,9 +959,10 @@ does not hold. A sink receives every track a track depends on (moq-streaming-for
|
|||
§4.1), so a dependency is all of them. A refinement track carries
|
||||
`wai_delivery_timeout_ms` (§8.6), a catalog field this extension defines under the `wai_`
|
||||
prefix of moq-streaming-format §4. The base track has `altGroup` 1 when the component has
|
||||
a floor: the floor is a track `<c>/floor` of `packaging` `"cmaf"`, in the same namespace,
|
||||
with `altGroup` 1, `renderGroup` 1, `codec`, `width` and `height`, and a sink that
|
||||
selects the floor subscribes to no layer track. The receipts tracks follow, one per media
|
||||
a floor: each floor rung's floor, in ladder order, is a track `<f>/floor` for its floor
|
||||
name *f* (§7.1), of `packaging` `"cmaf"`, in the same namespace, with `altGroup` 1,
|
||||
`renderGroup` 1, `codec`, `width` and `height`, and a sink that selects a floor
|
||||
subscribes to no layer track. The receipts tracks follow, one per media
|
||||
track in its order, each `{"isLive": false, "name": "<name>/receipts", "namespace",
|
||||
"packaging": "wai-receipts"}`. The catalog passes moq-streaming-format §4.1's check, and a
|
||||
packager writes no other. A package holds no receipt: the publisher signs each track's
|
||||
|
|
@ -956,7 +1097,8 @@ figure and class that do not cohere, or a legacy label (`receipt-energy`); a sig
|
|||
that does not verify (`receipt-signature`).
|
||||
|
||||
**9.2** Live windows chain by the parent link with parent kind `live-window`. A re-pack
|
||||
of an earlier package **MAY** name it with parent kind `re-pack`.
|
||||
of an earlier package **MAY** name it with parent kind `re-pack`, and one that takes its
|
||||
bases and refinements from it **MUST** (§11.5).
|
||||
|
||||
The reference live packer (`wai-pack live`, `wai_pack::live`) takes a source a frame at a
|
||||
time, cuts it with the segmenter of §4.3, which decides a frame once `lookahead_frames`
|
||||
|
|
@ -1041,23 +1183,29 @@ royalty-free by its standards body"; it is not a freedom-to-operate opinion.
|
|||
|
||||
policy.toml policy.json plan-<c>.txt index-<c>.wsix package.receipt.json
|
||||
objects/<content hash>.wai …
|
||||
floor/<c>/init.mp4 floor/<c>/<sha256>.m4s …
|
||||
floor/<f>/init.mp4 floor/<f>/<sha256>.m4s … (per floor rung, of floor name f)
|
||||
floor.listing (a supplied CMAF floor's listing, §7.7)
|
||||
hls/classic/… hls/<class>/… dash/… moq/catalog.json stages/<class>.json
|
||||
claims/<receipt_hash>.measurement.json (a metered package)
|
||||
LISTING
|
||||
|
||||
`policy.toml` is the policy as given; `policy.json` its effective canonical JSON
|
||||
(§3.2). `LISTING` is the listing (SPEC §3.1) of every other file, and its SHA-256 is the
|
||||
**pack digest**. A component or class id that cannot be one path segment of a listing,
|
||||
two class ids equal once `A`–`Z` are mapped to `a`–`z`, and a class named `classic` in
|
||||
any case (ids name paths, and paths fold case) are refused (`pack-id`).
|
||||
**pack digest**. A component or class id or a floor name (§7.1) that cannot be one path
|
||||
segment of a listing, two class ids or two floor names equal once `A`–`Z` are mapped to
|
||||
`a`–`z`, and a class named `classic` in any case (ids name paths, and paths fold case)
|
||||
are refused (`pack-id`).
|
||||
|
||||
**11.2** *Re-pack equivalence.* Given the same source bytes, effective policy, supplied
|
||||
floor, signing key and packager identity, a conforming reference packager **MUST**
|
||||
produce byte-identical package files on every machine, except the energy figure and
|
||||
measurement claim of a metered package and floor objects §7.5 covers. A verifier
|
||||
produce byte-identical package files on every machine and whichever fabric runs each
|
||||
step (§11.6), except the energy figure and measurement claim of a metered package and
|
||||
floor objects §7.5 covers. A verifier
|
||||
**MAY** pack again and compare listings. A packager whose identity differs is not held
|
||||
to this.
|
||||
to this. A re-pack from a parent (§11.5) is held to it with the parent among its inputs;
|
||||
and where the parent was packed by a conforming packager from the same source under base
|
||||
and refinement rungs of the same capabilities and parameters, its files are those of a
|
||||
pack that codes every piece, but for its receipt's parent and so `LISTING`.
|
||||
|
||||
**11.3** *Verification.* A verifier of a package, without its sources, applies these
|
||||
checks in this order and refuses at the first that fails; a check that reads files a
|
||||
|
|
@ -1077,7 +1225,7 @@ failed check names is not applied:
|
|||
effective canonical JSON, `royalty_free_only` forced on where `policy.json` says
|
||||
`true` (`policy-json-mismatch`; an override never turns it off, §3.2, so a
|
||||
`policy.json` that says `false` over a `policy.toml` that says `true` is refused
|
||||
here); its ids can name files (`pack-id`, §11.1); and under that effective policy it
|
||||
here); its ids and floor names can name files (`pack-id`, §11.1); and under that effective policy it
|
||||
asks for nothing a packager may not emit (§3.6's reason; under `royalty_free_only`, a
|
||||
patent-pool floor is `license-refused`).
|
||||
5. The receipt's components are the policy's, by id, in order, each of the policy's
|
||||
|
|
@ -1089,18 +1237,20 @@ failed check names is not applied:
|
|||
class are the component's; its string table is the component's id, the class ids
|
||||
in policy order, then each capability at its first use; its class records are the
|
||||
policy's classes, in order, with their `max_layer`, floor-only flag and `max_kbps`;
|
||||
every piece's kind is one a video piece has; its rules and moments are §8.1's from
|
||||
every piece's kind is one a video piece has, `none` exactly in a floor-only
|
||||
component (§7.8); its rules and moments are §8.1's from
|
||||
its kind and span; its entries are the rungs of its ladder (§5.7), by role,
|
||||
capability and layer: its base, each refinement in layer order, each floor; each
|
||||
capability and layer: its base, each refinement in layer order, each floor (a
|
||||
floor-only component's, its floors alone); each
|
||||
refinement's deadline is its plan's (§8.6); and its class plan is §8.1's from its
|
||||
entries;
|
||||
- the plan file holds the index's starts and its digest is the header's
|
||||
(`plan-mismatch`);
|
||||
- every entry's object is present (`object-missing`) and keeps the object rules of
|
||||
§6.3, its counted bytes included;
|
||||
- before any frame is decoded, each piece's floor object, where the policy has a floor
|
||||
rung and the object's payload reads as §7.2's framing, holds one unit per frame of
|
||||
its piece (`floor-object-mismatch`);
|
||||
- before any frame is decoded, each piece's floor objects, where the policy has floor
|
||||
rungs and an object's payload reads as §7.2's framing, hold one unit per frame of
|
||||
their piece (`floor-object-mismatch`);
|
||||
- every base and refinement, in entry order: a base decodes by its capability (a
|
||||
still is a PNG §5.7 reads, its one frame held for the piece), or it is
|
||||
`prefix-digest`; its manifest is the one §5.2 states for its piece's frames and its
|
||||
|
|
@ -1111,61 +1261,184 @@ failed check names is not applied:
|
|||
does not apply, ends the check;
|
||||
- then every decode-equivalent prefix decodes to its `prefix_digest`
|
||||
(`prefix-digest`);
|
||||
- when all of that holds, the floor: per piece, one floor object of the floor rung's
|
||||
capability whose renderer, settings, framing header, unit count and target are
|
||||
§7.2's (`floor-object-mismatch`) and which presents the piece's base
|
||||
(`floor-motif-mismatch`); its units each one temporal unit holding a frame, the
|
||||
first of the first piece carrying a sequence header, every sequence header the
|
||||
first's bytes, every shown key frame carrying one (`floor-av1-malformed`); each
|
||||
piece opening on a shown key frame, and no other unit being one unless the policy
|
||||
allows extra key frames (`floor-keyframe-mismatch`); and the CMAF files are the
|
||||
ones its temporal units give (§7.6, `floor-cmaf-mismatch`);
|
||||
- when all of that holds, the floors, floor rung by floor rung in ladder order: per
|
||||
piece, one floor object per floor rung, the rung's the *k*-th floor entry for the
|
||||
*k*-th floor rung, of the rung's capability, whose renderer, settings, framing
|
||||
header, unit count and target are §7.2's (`floor-object-mismatch`) and which
|
||||
presents the piece's base (`floor-motif-mismatch`), or, in a floor-only component,
|
||||
which carries no `derived_from` (`floor-object-mismatch`); its units each one temporal unit
|
||||
holding a frame, the first of the rung's first piece carrying a sequence header,
|
||||
every sequence header the first's bytes, every shown key frame carrying one
|
||||
(`floor-av1-malformed`); each piece opening on a shown key frame, and no other unit
|
||||
being one unless the policy allows extra key frames (`floor-keyframe-mismatch`); and
|
||||
the rung's CMAF files are the ones its temporal units give (§7.6,
|
||||
`floor-cmaf-mismatch`); or, for a supplied floor in the CMAF form (§7.7), `floor.listing`
|
||||
is a listing that hashes to `floor.supplied_listing`, `floor/<f>/init.mp4` is the file
|
||||
of its line `<f>/init.mp4` and reads as §7.7's initialization segment, and the
|
||||
listing's media segments of `<f>/`, in its order, are one per piece, piece *k*'s
|
||||
`floor/<f>/<its SHA-256>.m4s`, reading as §7.7's media segment, opening on a sync
|
||||
sample, each sample one frame and the first at the piece's start, its samples the
|
||||
piece's floor units without their temporal delimiters (`floor-cmaf-mismatch`);
|
||||
- then the catalogs are the ones the index, the objects and the floor give, and an
|
||||
index no catalog can be written for (a start past `2^53 − 1`) is refused
|
||||
(`catalog-mismatch`).
|
||||
7. A metered package's measurement claim (§9.4: `measurement-claim-missing`,
|
||||
`measurement-claim-invalid`); an object file no entry names (`object-unlisted`); a
|
||||
`measurement-claim-invalid`, a fabric record naming a step this extension does not
|
||||
define, §11.6, among the latter); an object file no entry names (`object-unlisted`); a
|
||||
catalog file past the ones given (`catalog-unexpected`); any other file
|
||||
(`unexpected-file`).
|
||||
|
||||
**11.4** *What this revision packs.* The reference packager packs one video component:
|
||||
bases of `wai.video.int_motion` (§5.5, §5.6) and stills of `wai.image.png` (§5.7);
|
||||
refinements of `wai.video.int_refine` over an `int_motion` base (§5.4); each piece by its
|
||||
ladder, a rule's ladder subset included (§5.7); and a supplied AV1 floor (§7.3). Its live
|
||||
packer packs the same pieces in windows, without a floor (§9.2). It refuses by name,
|
||||
ladder, a rule's ladder subset included (§5.7); and a supplied AV1 floor per floor rung,
|
||||
one or several (§7.1), in the OBU form (§7.3) or the CMAF form (§7.7); and floor-only
|
||||
components (§7.8). Its live
|
||||
packer packs the same pieces in windows, without a floor (§9.2). It runs `base-search`,
|
||||
`sse` and `object-hash` on a GPU that qualifies, where it is built to and asked to, and
|
||||
every other step on the CPU (§11.6). It refuses by name,
|
||||
before writing anything, what it does not pack yet: another medium
|
||||
(`pack-media-unsupported`), a floor to encode (`pack-floor-encode-unsupported`), and a rung
|
||||
of a capability it does not encode, or a refinement over a base it does not refine
|
||||
(`pack-capability-unsupported:<cap>`, every one naming the capability of the rung it does
|
||||
not pack); and a component with no source (`pack-source-missing`) or whose source's frame
|
||||
rate is not its timebase (`pack-source-rate`). Where its own work fails it
|
||||
refuses by name too, and writes nothing: an encode the encoder refuses, such as a piece
|
||||
refuses by name too, and leaves nothing written (a file it wrote before the refusal is
|
||||
removed): an encode the encoder refuses, such as a piece
|
||||
past the samples it holds, or an object it wrote that does not count (`pack-encode`); a
|
||||
piece past `2^32 − 1` units (`pack-piece-length`); catalogs it cannot write, the case
|
||||
§11.3 refuses as `catalog-mismatch` (`pack-catalog`); a meter that was asked for and
|
||||
gives no figure, a span too short to reach a microjoule or a figure past `2^64 − 1`
|
||||
(`pack-meter`, never an unmetered package in its place); a measurement claim it cannot
|
||||
write (`pack-measurement-claim`); and a listing it cannot write (`pack-listing`). It
|
||||
write (`pack-measurement-claim`); a listing it cannot write (`pack-listing`); and a source
|
||||
that does not read the same twice (`pack-source-changed`) or a supplied floor stream that
|
||||
does not (`floor-listing-mismatch`): it reads each a frame or a temporal unit at a time,
|
||||
once to cut and check it and once to code it, and holds neither whole. What it holds is
|
||||
not a function of the source's length but for a record per piece and per entry and a byte
|
||||
per frame of each floor (whether it starts at a key frame): the frames of the pieces it
|
||||
codes at once, which it chooses, each piece coded alone (§4.1), so the package is the same
|
||||
for any number; one piece of each floor; and, for the catalogs, one object at a time,
|
||||
read back from where it wrote it. Each file is written whole as it is made. It
|
||||
writes a package only into a directory that is new or holds nothing, and refuses any
|
||||
other before it packs (`pack-out-not-empty`): written among another package's files, it
|
||||
would leave behind the ones it does not write.
|
||||
|
||||
**11.5** *Re-pack from a parent.* A packager **MAY** take every piece's base and
|
||||
refinements from an earlier package, its **parent**, instead of coding them, so a
|
||||
component's integer base is coded once and taken by every package made from the same
|
||||
source after it. It reads the source as for a pack that codes (§4: its pieces and their
|
||||
kinds, §4.3, §4.6, are this source's), and then, before it writes anything, **MUST**
|
||||
refuse:
|
||||
|
||||
1. a parent that does not verify as §11.3 verifies a package (`repack-parent-invalid`,
|
||||
with the reason the parent fails for);
|
||||
2. a parent with no component of the component's id, media and timebase, or whose pieces
|
||||
are not this source's, piece by piece: another number of pieces, or a piece of
|
||||
another start, duration, flags or kind, or whose ladder (§5.7, under the parent's
|
||||
policy) codes its base and refinements in rungs of other capabilities or other
|
||||
parameters (§3.3) than this policy's ladder for the piece, in layer order, or with
|
||||
another deadline (§8.6) (`repack-parent-mismatch`);
|
||||
3. a piece whose prefixes, each the parent's base decoded and its refinements applied in
|
||||
layer order by a staged-delivery §4 sink, do not reconstruct this source with the
|
||||
`sse` the parent's entry states (§6.6) (`repack-source-mismatch`): a parent packed
|
||||
from another source.
|
||||
|
||||
It then writes, for each piece, the parent's base and refinement objects and their
|
||||
entries' fields (§6.2: content hash, lengths, capability, role, layer, tier, prefix
|
||||
digest, `sse`, deadline) in place of coding them, and the floors as it would; its
|
||||
receipt names the parent's receipt with parent kind `re-pack` (§9.1). A verifier holding
|
||||
the parent **MAY** check a re-pack against it: the parent verifies
|
||||
(`repack-parent-invalid`), the re-pack's receipt names the parent's receipt with parent
|
||||
kind `re-pack`, and each component's pieces are the parent's, piece by piece, of the same
|
||||
start, duration, kind and flags, whose base and refinement entries are the parent's
|
||||
entries, every field the same but the entry's place in its index: the entry it refines
|
||||
counted from its piece's first entry, and the duplicate flag, which an index's own
|
||||
entries decide (`repack-parent-mismatch`). A re-pack holds no source of its parent's
|
||||
bases; what binds a reused base to this source is the third check, and what binds it to
|
||||
the parent is the receipt.
|
||||
|
||||
**11.6** *Steps and fabrics.* The steps of a pack and a verify are named by these terms:
|
||||
|
||||
| Step | What it computes |
|
||||
|---|---|
|
||||
| `source-decode` | a source's frames from a coded rendition, before a pack, by a producer's tool (§4.7 reads the result) |
|
||||
| `source-read` | a source's frames or samples (§4.7) |
|
||||
| `segment` | the pieces (§4.3) and their kinds (§4.6) |
|
||||
| `base-search` | the integer base's motion search (§5.5) |
|
||||
| `base-code` | the rest of the integer base (§5.5), its step (§5.6) and a still (§5.7) |
|
||||
| `refine-code` | refinement layers, each applied by the reference sink (§5.4) |
|
||||
| `sse` | an entry's `sse` (§6.6) |
|
||||
| `object-hash` | an object's content hash (§5.3) |
|
||||
| `digest` | the SHA-256 digests: decoded-output digests (§6.5), listings, file names |
|
||||
| `floor-read` | a supplied floor's units, CMAF boxes and listing (§7.3, §7.7) |
|
||||
| `floor-decode` | a floor's units decoded to pictures (item 4 below) |
|
||||
| `index` | the stage indexes, catalogs, receipt and claim (§6, §8, §9) |
|
||||
| `io` | reading and writing files |
|
||||
|
||||
Every step is a function of its inputs, defined in integers (and `floor-decode` by the
|
||||
AV1 decoding process), and none names a fabric:
|
||||
|
||||
1. *Packages.* A packager **MAY** run a step on any fabric whose output is the step's
|
||||
for every input. It **MUST NOT** run a step on a backend that has not given the
|
||||
reference implementation's output on the qualifying inputs before it is used (the
|
||||
reference packager's: the frame pairs of `wai_pack::fabric::qualifying_frames` for
|
||||
the motion search, and seeded inputs for SSE and BLAKE3, on each device it opens), nor
|
||||
at all on one that computes otherwise than the step's definition, such as in floating
|
||||
point where the step is defined in integers, or with a learned approximation. A
|
||||
backend that cannot take a call (a size, a lost device) leaves it to the CPU, with the
|
||||
same output. So §11.2 holds across fabrics: the package files are the same bytes
|
||||
whichever fabric ran each step, and only a metered package's measurement claim says
|
||||
where they ran.
|
||||
2. *Verdicts.* A verifier's verdict and its reason are functions of the package
|
||||
(§11.3). A verifier **MAY** compute a check on another fabric where the package states
|
||||
the value the check compares with, such as an object's content hash: a match shows
|
||||
that the fabric computed the check, and a mismatch **MUST** be computed again on the
|
||||
reference implementation before it is reported. Every other check runs on a backend
|
||||
held to the reference's output as item 1 holds a packager's. A verdict that changes
|
||||
with the fabric is the verifier's fault, never the package's.
|
||||
3. *Hardware encoders.* A hardware encoder's output depends on the chip, its firmware
|
||||
and driver, and its settings, and no rule fixes it, so no step of this extension is
|
||||
computed by one. A floor a packager is given is an input, whatever made it (§7.3,
|
||||
§7.7); a floor a packager encodes on such hardware is one §7.5 covers, whose
|
||||
`derived_from.renderer` names the encoder, its version, and the device and driver that
|
||||
ran it, and §11.2 does not hold its bytes.
|
||||
4. *Decoding a floor.* The AV1 decoding process fixes every output sample of a stream
|
||||
whose sequence header has `film_grain_params_present` 0, and of every stream before
|
||||
film grain is applied (AV1 §7.2), so conformant decoders on any fabric give the same
|
||||
pictures of a floor's units. A floor states no decoded-output digest (§7.4), so a
|
||||
decode of it has no stated value to compare with: a verifier that decodes floors
|
||||
**MUST NOT** refuse a package for what a decoder other than the reference gives, nor
|
||||
read a hardware decoder's error or concealment as a verdict. A hardware decoder's
|
||||
frames may be held in a wider format than the stream's bit depth (a 10-bit sample
|
||||
shifted into the high bits of a 16-bit word); a reader of them recovers each sample
|
||||
exactly or refuses the frame, and never rounds. (Informative: `wai-pack floor-decode`
|
||||
reports each piece's digest from either decoder, for comparing them.)
|
||||
5. *The fabric record.* A metered package's measurement claim **MAY** carry a fabric
|
||||
record (jwp-receipts §2.4.8, energy-measurement §6.3): each step the metered span ran,
|
||||
with every fabric that ran it and its backend, and the figure's share on each. Its
|
||||
steps are this section's terms (§11.3 step 7). An unmetered package carries no claim,
|
||||
so no record: which fabric ran a step is not part of the package, and two packages
|
||||
that differ only in where their steps ran are the same files.
|
||||
|
||||
## 12. Reasons
|
||||
|
||||
| Reason | Where |
|
||||
|---|---|
|
||||
| `policy-toml` (not UTF-8, a byte-order mark, not TOML 1.0, nesting past 32), `policy-float`, `policy-datetime`, `policy-negative`, `policy-integer-range` | §3.2 |
|
||||
| `policy-unknown-key`, `policy-missing-key`, `policy-type`, `policy-value`, `policy-schema` | §3.3 |
|
||||
| `policy-too-many-components`, `policy-too-many-rungs`, `policy-too-many-classes`, `policy-too-many-rules`, `policy-too-many-moments`, `policy-duplicate-id`, `policy-lead`, `policy-open-gop`, `policy-piece-bounds`, `policy-ladder-component`, `policy-refines-role`, `policy-dangling-refines`, `policy-refines-cycle`, `policy-refines-base`, `policy-refines-branch`, `policy-no-base`, `policy-class-layer`, `policy-rule-ladder`, `policy-layer-lag`, `policy-too-many-deadlines`, `policy-deadline-unreachable` | §3.4 |
|
||||
| `policy-too-many-components`, `policy-too-many-rungs`, `policy-too-many-classes`, `policy-too-many-rules`, `policy-too-many-moments`, `policy-duplicate-id`, `policy-lead`, `policy-open-gop`, `policy-piece-bounds`, `policy-ladder-component`, `policy-refines-role`, `policy-dangling-refines`, `policy-refines-cycle`, `policy-refines-base`, `policy-refines-branch`, `policy-no-base`, `policy-floor-only`, `policy-class-layer`, `policy-rule-ladder`, `policy-layer-lag`, `policy-too-many-deadlines`, `policy-deadline-unreachable` | §3.4 |
|
||||
| `capability-unknown`, `capability-not-payload`, `capability-candidate`, `capability-role`, `license-unclassified`, `license-refused`, each naming the capability (and the licence label) | §3.6 |
|
||||
| `y4m-header`, `y4m-interlaced`, `y4m-colorspace`, `y4m-size-change`, `y4m-truncated`, `wav-format`, `wav-truncated` | §4.7 |
|
||||
| `index-magic`, `index-version`, `index-truncated`, `index-reserved`, `index-media-class`, `index-role`, `index-tier`, `index-string`, `index-trailing` | §6.2 |
|
||||
| `index-timebase`, `window-start`, `piece-order`, `piece-entries`, `piece-kind`, `entry-flags`, `entry-order`, `layer-role`, `refines-outside-window`, `refines-mismatch`, `layer-gap`, `tier-entropy-passthrough`, `tier-weakest`, `tier-mismatch`, `digest-without-tier`, `deadline-role`, `floor-derived-from`, `dup-length`, `dup-flag`, `class-plan`, `class-flags` | §6.3, §6.4 |
|
||||
| `object-length`, `object-hash`, `non-envelope-entry`, `object-lengths`, `object-capability`, `object-refines`, `object-derived-from` | §6.3 |
|
||||
| `floor-missing`, `floor-listing-mismatch`, `floor-stream-missing`, `floor-av1-malformed`, `floor-frame-count`, `floor-keyframe-mismatch` | §7.1, §7.3 |
|
||||
| `floor-stream-missing`, `floor-cmaf-malformed`, `floor-cmaf-timing`, `floor-cmaf-pieces` | §7.7 |
|
||||
| `floor-keyframe-mismatch`, `floor-piece-length` | §7.8 |
|
||||
| `floor-object-mismatch`, `floor-motif-mismatch`, `floor-av1-malformed`, `floor-keyframe-mismatch`, `floor-cmaf-mismatch`, against a package | §11.3 |
|
||||
| `receipt-json`, `receipt-version`, `receipt-index-count`, `receipt-index-digest`, `receipt-stage-root`, `receipt-component`, `receipt-policy-digest`, `receipt-counts`, `receipt-parent-kind`, `receipt-energy`, `receipt-signature` | §9.1 |
|
||||
| `measurement-claim-missing`, `measurement-claim-invalid` | §9.4 |
|
||||
| `unexpected-file`, `listing-missing`, `listing-mismatch`, `policy-digest-mismatch`, `policy-json-mismatch`, `policy-component-mismatch`, `index-missing`, `index-policy-mismatch`, `plan-mismatch`, `object-missing`, `object-manifest`, `prefix-digest`, `catalog-mismatch`, `catalog-unexpected`, `object-unlisted` | §11.3 |
|
||||
| `pack-id`, `pack-source-missing`, `pack-source-rate`, `pack-media-unsupported`, `pack-floor-encode-unsupported`, `pack-capability-unsupported` (naming the capability) | §11.1, §11.4 |
|
||||
| `pack-encode`, `pack-piece-length`, `pack-catalog`, `pack-meter`, `pack-measurement-claim`, `pack-listing`, `pack-out-not-empty` | §11.4 |
|
||||
| `pack-encode`, `pack-piece-length`, `pack-catalog`, `pack-meter`, `pack-measurement-claim`, `pack-listing`, `pack-source-changed`, `pack-out-not-empty` | §11.4 |
|
||||
| `repack-parent-invalid`, `repack-parent-mismatch`, `repack-source-mismatch` | §11.5 |
|
||||
|
|
|
|||
182
wai/extensions/platform-decode.md
Normal file
182
wai/extensions/platform-decode.md
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
# WAI Extension: Platform Decode Capabilities
|
||||
|
||||
> Status: Draft. Maps two web platform interfaces, Media Capabilities `decodingInfo()`
|
||||
> and WebCodecs `isConfigSupported()` with its hardware-acceleration preference, onto the
|
||||
> capability report of [browser-sink](browser-sink.md) §3: what a sink asks the platform
|
||||
> about an entry, read from the entry's payload (§2), and how the answers enter the report
|
||||
> (§3). The answers are hints. They order the alternatives a page can decode, inside
|
||||
> browser-sink's routing, by SPEC §4's energy order (browser-sink §4.7); they never change a
|
||||
> route, are never an energy figure and are never evidence of hardware. No manifest member
|
||||
> and no wire format.
|
||||
> Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174.
|
||||
|
||||
## 1. Scope
|
||||
|
||||
A sink running in a web platform can decode a standard video codec through the
|
||||
platform's own decoders, which may be fixed-function hardware, or through a decoder of its
|
||||
own: WebAssembly, or a learned reconstruction such as WAI's integer codecs. The platform
|
||||
says, for a configuration, whether it can decode it, whether it can do so without
|
||||
dropping frames, and whether it judges the decode power-efficient (Media Capabilities
|
||||
§2.2). A learned reconstruction runs on the sink's CPU, GPU or neural engine, and what it
|
||||
draws there is known only where it has been measured. This extension says how a WAI sink
|
||||
asks the platform about an entry, and how it enters what the platform says in browser-sink's
|
||||
capability report, where the routing (browser-sink §4) decides what the page can decode and
|
||||
the energy order (SPEC §4, *Energy order*; browser-sink §4.7) orders what it can.
|
||||
|
||||
It reads these documents:
|
||||
|
||||
- Media Capabilities, W3C Working Draft, 18 October 2026: `MediaDecodingConfiguration`,
|
||||
`VideoConfiguration`, `MediaCapabilitiesInfo` (§2.1, §2.2), the decoding-info algorithm
|
||||
(§2.3.2), and the privacy considerations (§3);
|
||||
- WebCodecs, W3C Working Draft, 19 October 2026: `VideoDecoderConfig` (§7.6),
|
||||
`isConfigSupported()` and Check Configuration Support (§4, §7.1), and
|
||||
`HardwareAcceleration` (§7.9);
|
||||
- the WebCodecs AV1 codec registration (Group Note Draft, 13 October 2026) and HEVC codec
|
||||
registration (Group Note Draft, 8 June 2026), which define the codec strings by the AV1
|
||||
ISOBMFF binding (AV1 Codec ISO Media File Format Binding v1.3.0, §5) and by ISO/IEC
|
||||
14496-15 Annex E.3.
|
||||
|
||||
ISO/IEC 14496-15 was not read. The HEVC codec string §2 writes follows the form the HEVC
|
||||
registration describes (four dot-separated fields of Annex E.3 and the constraint bytes
|
||||
after them), checked against an open-source parser of that form (Chromium's
|
||||
`ParseHEVCCodecId`) and against a platform that decodes with it (§5).
|
||||
|
||||
## 2. The query
|
||||
|
||||
For an entry whose capability the table maps, the sink forms the query from the entry's
|
||||
payload. It **MUST NOT** form it from anything the envelope declares apart from the
|
||||
payload: the codec string a payload's encoder declared can differ from the stream
|
||||
(§5 records one that did).
|
||||
|
||||
| capability | codec string | size | frame rate | bit rate |
|
||||
|---|---|---|---|---|
|
||||
| `wai.video.av1`, `wai.video.av1.lossless` | from the first temporal unit's sequence header OBU: `av01.`, the profile, the level (two digits) and the tier (`M` or `H`), the bit depth (two digits); then the monochrome flag, the chroma subsampling (three digits), the colour primaries, transfer characteristics and matrix coefficients (two digits each, 01 each where the sequence header carries no colour description), and the full-range flag; the part after the bit depth omitted where it is exactly `.0.110.01.01.01.0` (AV1-ISOBMFF §5) | `w` × `h` of SPEC §5's AV1 framing | `fps_num` / `fps_den` of the framing | the payload's bits × the frame rate ÷ `n`, rounded up |
|
||||
| `wai.video.hevc` | from the first SPS NAL unit (H.265 §7.3.2.2, §7.3.3): `hev1.`; the profile space as nothing, `A`, `B` or `C`, then `general_profile_idc` in decimal; the 32 `general_profile_compatibility_flag`s as one hexadecimal value with flag *j* at bit *j*; `L` or `H` for `general_tier_flag`, then `general_level_idc` in decimal; then the six bytes from `general_progressive_source_flag` on, each in hexadecimal, trailing zero bytes omitted. `hev1` because an Annex B stream carries its parameter sets in band | `pic_width_in_luma_samples` × `pic_height_in_luma_samples`, less the conformance window; the coded size before it | none in the payload: the rate the sink will present at, where it has one | the payload's bits × the frame rate ÷ the pictures the stream holds, rounded up |
|
||||
|
||||
A capability the table does not map is not queried: the sink decodes it, or does not, as
|
||||
it otherwise would, and the order of §4 places it by its class alone.
|
||||
|
||||
The sink asks:
|
||||
|
||||
- `navigator.mediaCapabilities.decodingInfo()` with a `MediaDecodingConfiguration` of
|
||||
`type` `"media-source"` and a `video` member: `contentType`
|
||||
`video/mp4; codecs="<codec string>"`, the size, the bit rate and the frame rate. Where
|
||||
the query has no frame rate it does not ask: `framerate` and `bitrate` are required
|
||||
members, and a sink **MUST NOT** invent them.
|
||||
- `VideoDecoder.isConfigSupported()` with a `VideoDecoderConfig` of the codec string, the
|
||||
coded size as `codedWidth` and `codedHeight`, and the `hardwareAcceleration` the sink
|
||||
will configure, when it decodes the entry through WebCodecs.
|
||||
|
||||
WebCodecs gives no smoothness or power answer, and Media Capabilities has no WebCodecs
|
||||
decoding type. A sink that decodes through WebCodecs asks `isConfigSupported()` first, and
|
||||
asks `decodingInfo()` of the `media-source` query of the same configuration only once
|
||||
`isConfigSupported()` answers supported, and only for a codec string browser-sink §4.2's
|
||||
registry gate passes: Media Capabilities is a further question about a decoder already
|
||||
found (http-delivery §12.2). It reads `smooth` and `powerEfficient` from that answer as
|
||||
hints and nothing more (§3).
|
||||
|
||||
## 3. The answers
|
||||
|
||||
A page enters what the platform said in browser-sink §3's capability report, one answer per
|
||||
codec string, `{supported, smooth, power_efficient, probe}`, and in no other shape
|
||||
(`platform_decode::report_answer`):
|
||||
|
||||
- **`supported`** is `isConfigSupported()`'s answer for the preference the sink will
|
||||
configure, alone. A Media Capabilities `supported` is not read: a `media-source` answer is
|
||||
about the media-source pipeline, its container support included, not about the decoder
|
||||
WebCodecs found, so a `decodingInfo()` answer of `supported: false` overrides nothing.
|
||||
`supported: false` makes the user agent's leg unavailable for that object
|
||||
(browser-sink §4.3 step 4.5, `ua_unsupported`); the object then routes to a host or a
|
||||
wasm decoder where the page holds one, or nowhere. It is not a decode error and not a
|
||||
reason to try the decode anyway.
|
||||
- **`smooth` and `power_efficient`** are `decodingInfo()`'s, entered only where
|
||||
`supported` is true, and `null` otherwise or where nothing was asked. They order the
|
||||
alternatives a page can decode, and only for an object routed `ua` (browser-sink §4.7): an
|
||||
object the page routes to a host or a wasm decoder, because the user agent's tier is below
|
||||
what the object needs (AV1's user-agent tier is `none`, browser-sink §4.1) or because no
|
||||
probe ran for a codec whose need is `decode-equivalence` (browser-sink §4.4), is not
|
||||
ranked on an answer about a decoder that will not run it. `powerEfficient` is the user
|
||||
agent's judgement that the decode's power draw is optimal, its definition left to the user
|
||||
agent, which **SHOULD NOT** mark hardware decoding power-efficient by default (Media
|
||||
Capabilities §2.2). It is a hint, not a measurement and not evidence of hardware. A sink
|
||||
**MUST NOT** write it, or anything derived from it, into a receipt, a measurement claim or
|
||||
an energy figure, and **MUST NOT** present it as an acquisition class
|
||||
([energy-measurement](energy-measurement.md) §2).
|
||||
- **`probe`** is `not_run`: this extension runs no probe (browser-sink §4.4).
|
||||
|
||||
**The hardware-acceleration preference.** `prefer-hardware` and `prefer-software` are
|
||||
hints a user agent may ignore for any reason (WebCodecs §7.9). An `isConfigSupported()`
|
||||
answer of `supported: true` with `prefer-hardware` says the platform accepts that
|
||||
configuration with that preference; it is not evidence that the decode runs on hardware,
|
||||
and a sink **MUST NOT** report it as such.
|
||||
|
||||
**Privacy.** The answers can be used to fingerprint the sink (Media Capabilities §3.2). A
|
||||
sink **SHOULD NOT** send them, or the order they give, to any party; the order is computed
|
||||
where the answers are. A platform that fakes its capabilities to resist fingerprinting
|
||||
(§3.2 recommends a fixed set rather than always yes or always no) gives answers the order
|
||||
uses as given: the result is a choice that platform made, and the decode still conforms.
|
||||
|
||||
## 4. The order the answers feed
|
||||
|
||||
This extension defines no order of its own, and no second meaning of "the sink advertises
|
||||
`cap`". What a page advertises for an object is its route (browser-sink §4.3, SPEC §4).
|
||||
Among alternatives the page can route, SPEC §4's energy order applies, as browser-sink §4.7
|
||||
writes it: `first` for classical decode routed `ua` that the platform answers supported,
|
||||
smooth and power-efficient, then `smooth`, then `classical` (any other routed classical
|
||||
decode, and a learned one with a qualifying sink-metered figure in browser-sink §6's energy
|
||||
entry), then `learned-without-budget`. A `WAI1` envelope's capability and fallback are two
|
||||
such alternatives. The order reads no figure's value (browser-sink §6).
|
||||
|
||||
## 5. Conformance
|
||||
|
||||
The reference implementation is `wai::platform_decode` (`wai/wai-rs/src/platform_decode.rs`):
|
||||
`query` forms §2's query, and `report_answer` §3's answer. It calls no platform: the
|
||||
answers are inputs. The vectors are in `wai/platform-decode-conformance/`: `queries.json`,
|
||||
two floor objects of the packaging corpus, whose codec string is the one the packager wrote
|
||||
into its HLS master, and an HEVC SPS the platform's encoder wrote through WebCodecs,
|
||||
configured with `prefer-hardware`. The order's vectors are the `choices` of
|
||||
[`live-conformance/browser/`](../live-conformance/browser/).
|
||||
|
||||
An implementation conforms when it gives every query's members. The reference's tests
|
||||
(`platform_decode::tests`) also hold the AV1 codec string to the AV1-ISOBMFF binding's own
|
||||
examples, derive every floor of the packaging corpus to the codec string and size the
|
||||
packager wrote into the pack's HLS master, read constructed SPSs with sub-layers and with
|
||||
emulation-prevention bytes, and hold `report_answer` to §3.
|
||||
|
||||
`wai-web`'s `platform_decode` bundle binds the same code for a page (`platformQuery`,
|
||||
`platformAnswer`, and `platformChoose`, browser-sink §4.7's choice), and
|
||||
`wai-web/demo/platform_decode.html` (`verify_platform_decode.mjs`) runs it against a
|
||||
browser's platform: every AV1 floor object of the packaging corpus, queried and decoded
|
||||
through WebCodecs with the configuration its payload's query gives; an HEVC stream the
|
||||
platform's encoder writes, queried from that stream and decoded back; and the choice between
|
||||
a learned alternative and an AV1 floor object, from the report the page built from those
|
||||
answers. Every query the page asks is read from a payload.
|
||||
|
||||
Measured 2026-10-22 with that page on an arm64 desktop (macOS 27.0.1), in headless
|
||||
Chromium 148 and a Chromium-based browser at 155, with the same results in both, before the
|
||||
page's order moved into browser-sink's routing:
|
||||
|
||||
- every AV1 floor object of the packaging corpus (64×36, `av01.0.00M.08`) decoded every
|
||||
temporal unit through WebCodecs. The platform answered `supported` and `smooth`, not
|
||||
`powerEfficient`, and did not accept `prefer-hardware` at that size;
|
||||
- the platform's HEVC encoder, configured as `hvc1.1.6.L93.B0` for 640×360 with
|
||||
`prefer-hardware`, wrote an SPS with `general_level_idc` 63, and the codec string it
|
||||
declared for its output repeated the configured one. The query read `hev1.1.6.L63.B0` from
|
||||
the stream; the platform accepted that configuration with both preferences, answered
|
||||
`supported`, `smooth` and `powerEfficient`, and decoded every picture back with it.
|
||||
|
||||
The choice step of the page was rewritten with the order and has not been run in a browser
|
||||
in this revision; a 1920×1080 answer the earlier page reported was for a configuration
|
||||
written by hand, not read from a payload, and is not restated. Not measured: a platform
|
||||
without hardware AV1 or HEVC decode, a platform that fakes its answers, and any engine but
|
||||
Chromium's.
|
||||
|
||||
## 6. Security and privacy considerations
|
||||
|
||||
The answers are fingerprinting surface (§3, *Privacy*). The order reads them where they
|
||||
are produced and sends nothing. The query reads only the payload's headers; a malformed
|
||||
payload gives no query, and the entry is decoded, or not, as it would be without this
|
||||
extension. An encoder cannot raise an entry's rank by what it declares: the codec string
|
||||
is read from the payload, the class from the registry, the route from browser-sink's
|
||||
routing, and a learned entry ranks with classical decode only on a sink-metered figure the
|
||||
sink holds, never on one an envelope carries.
|
||||
|
|
@ -72,8 +72,9 @@ There is no tolerance parameter: the content hash matches or it does not.
|
|||
## 5. Energy is measured, never invented
|
||||
|
||||
`joules_micro` per step is **carried** from a meter, exactly as the other
|
||||
receipts carry it: `wai_prov` measures the real CPU energy of each encode
|
||||
(IOReport via macmon, no sudo) and signs the figure. An unmetered step
|
||||
receipts carry it: `wai_prov` brackets the CPU energy of each encode against
|
||||
an idle baseline, from the processor power a non-privileged reader reports (no
|
||||
sudo), and signs the figure. An unmetered step
|
||||
carries the unmetered marker `0`, which a reader presents as unmetered and never
|
||||
totals as zero joules. The receipt never derives energy it did not measure.
|
||||
|
||||
|
|
@ -81,10 +82,11 @@ Each step's figure carries its acquisition class as an optional label
|
|||
([energy-measurement](energy-measurement.md) §2.8), because the steps of one
|
||||
lineage can be metered by different parties in different ways. A labelled step
|
||||
binds its class into its leaf hash; a step with no label keeps its legacy leaf
|
||||
and its figure is unlabelled. `wai_prov` labels each encode it measures
|
||||
`OnChipCounter`, with its declared uncertainty, and records an encode whose
|
||||
measurement fails the §4 ratio or the power reader's liveness (§3.1) as
|
||||
unmetered, and writes a measurement report for each step it measured
|
||||
and its figure is unlabelled. `wai_prov` labels each encode it brackets
|
||||
`ModelBased`, because its samples are a power another program reports, not a
|
||||
counter it reads and shows live ([energy-measurement](energy-measurement.md)
|
||||
§3.1), and records an encode whose bracket fails the §4 ratio or the reported
|
||||
power's liveness (§3.1) as unmetered, and writes a measurement report for each step it measured
|
||||
(energy-measurement §6). With `--carry` it measures nothing: it carries each
|
||||
figure, and its label, from an earlier receipt for the same lineage, which must
|
||||
verify and whose steps must have the same operations, hashes and work, and writes
|
||||
|
|
|
|||
|
|
@ -558,26 +558,28 @@ reconstruction and decides nothing.
|
|||
|
||||
`to_json()` / `from_json()` give the portable, sorted-key JSON an out-of-band
|
||||
verifier reads. `wai_quantum_meter <circuit.wqc> [receipt.json]` fills the
|
||||
energy honestly: on a host that exposes its SoC's power interface to
|
||||
unprivileged readers it samples CPU power through the open-source `macmon`
|
||||
reader (no elevated privileges), brackets a reconstruction burst against an idle baseline, and writes the
|
||||
**measured** marginal microjoules — never a fabricated number; where no meter is
|
||||
present, or the reader's rail is not live in both brackets
|
||||
([energy-measurement](energy-measurement.md) §3.1), or the §4 ratio or the §2.3
|
||||
uncertainty cannot support a figure, it emits an UNMETERED receipt, whose `joules_micro` carries the
|
||||
energy honestly: it samples the CPU power the open-source `macmon` reader
|
||||
reports (no elevated privileges), brackets a reconstruction burst against an idle
|
||||
baseline, and writes the marginal microjoules — never a fabricated number; where
|
||||
no reader is present, or the reported power does not move in both brackets
|
||||
([energy-measurement](energy-measurement.md) §3.1), or the §4 ratio cannot
|
||||
support a figure, it emits an UNMETERED receipt, whose `joules_micro` carries the
|
||||
unmetered marker `0` ([energy-measurement](energy-measurement.md) §2), never a
|
||||
figure of zero joules, with the exact work intact; a reader presents it as
|
||||
unmetered, and `POST /verify` renders it as `"joules_micro": null`. Beside a
|
||||
measured figure the meter writes a measurement report (energy-measurement §6). A measured figure is sealed with its acquisition class as a
|
||||
figure the meter writes a measurement report (energy-measurement §6). A figure is sealed with its acquisition class as a
|
||||
`quantum_energy::Labelled` receipt ([energy-measurement](energy-measurement.md)
|
||||
§2.8): `OnChipCounter` with its declared uncertainty, inside the signature,
|
||||
§2.8): `ModelBased`, inside the signature, because the samples are a power the
|
||||
reader reports from a channel it selects, not a counter the meter reads and shows
|
||||
live ([energy-measurement](energy-measurement.md) §3.1). It is
|
||||
verified with `Labelled::verify` / `Labelled::verify_reconstruction`. The
|
||||
`wai-quantum verify` CLI and the HTTP handler's `POST /verify` accept a receipt
|
||||
in either form (`quantum_energy::MaybeLabelled`) and restate its class. A receipt
|
||||
sealed without a label keeps its legacy bytes and its figure is unlabelled. The
|
||||
meter decodes the circuit from its container, so the receipt binds the capability
|
||||
the file names. A representative run on the reference QFT-5 vector: **≈43 µJ
|
||||
measured per reconstruction, `work_amp_updates = 672`** (21 ops × 2⁵), sealed and
|
||||
the file names. A representative run on the reference QFT-5 vector, at a revision
|
||||
that labelled the same reported power `OnChipCounter`: **≈43 µJ per
|
||||
reconstruction, `work_amp_updates = 672`** (21 ops × 2⁵), sealed and
|
||||
self-verified.
|
||||
|
||||
Because a quantum computation carries no speedup here, this is exactly the value
|
||||
|
|
|
|||
|
|
@ -124,8 +124,9 @@ requires); `verify_reconstruction`
|
|||
recovers the stack from the `WQTT` payload and confirms hash, shape, work, and
|
||||
reconstruction independently. Chains via `parent_receipt_hash`. The JWP Merkle +
|
||||
Ed25519 are reused verbatim — the same shape as `QuantumReceipt`
|
||||
([quantum-sim](quantum-sim.md)) and the world/provenance receipts. Measured
|
||||
energy comes from the same IOReport path as `wai_quantum_meter`; never fabricated.
|
||||
([quantum-sim](quantum-sim.md)) and the world/provenance receipts. Its energy
|
||||
comes from the same bracket as `wai_quantum_meter`'s, `ModelBased` over a
|
||||
reported power ([energy-measurement](energy-measurement.md) §3.1); never fabricated.
|
||||
The figure's acquisition class rides beside it as an optional signed label
|
||||
([energy-measurement](energy-measurement.md) §2.8): absent, the receipt keeps
|
||||
its legacy bytes and the figure is unlabelled; present, it is inside the
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ agree:
|
|||
| id | object | covers |
|
||||
|---|---|---|
|
||||
| O1 | `WAI1`, the pinned `wai.neural.int_mlicpp` vector of `wai-rs/tests/vectors/int_mlicpp/` | a `WAI1` head; a proof that covers the whole object |
|
||||
| O2 | `WAI2` image: that pinned rendition, AVIF, JPEG XL and PNG of the one picture it decodes to | pins: held, not held |
|
||||
| O2 | `WAI2` image: that pinned rendition, AVIF, JPEG XL and PNG of the one picture it decodes to | pins: held, not held, in the table's order (a deployer policy); and the default order, SPEC §4's energy order, which picks AVIF before the learned rendition |
|
||||
| O3 | `WAI2` video: AV1 at two rates, HDR10+ metadata bound to the higher by `companion_of_id` | range plans; a companion; tampering |
|
||||
| O4 | `WAI2` whose first selectable entry states no `sha256` | `component_digest_absent`: the whole object, the same pick |
|
||||
| O5 | a small `WAI1` | fetched whole (§5.4) |
|
||||
|
|
|
|||
|
|
@ -112,9 +112,10 @@
|
|||
{"name":"response_200_whole","op":"response","input":{"request":[1009,3593],"status":200,"body_len":53794,"envelope_len":53794},"expect":{"ok":[0,53793]}},
|
||||
{"name":"response_past_end","op":"response","input":{"request":[0,16368],"status":206,"content_range":"bytes 0-899/900","body_len":900,"envelope_len":900},"expect":{"ok":[0,899]}},
|
||||
{"name":"response_206_unasked","op":"response","input":{"request":null,"status":206,"content_range":"bytes 0-9/900","body_len":10,"envelope_len":900},"expect":{"error":"http_status_unexpected"}},
|
||||
{"name":"select_O2_held","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"pick":0,"skipped":[]}},
|
||||
{"name":"select_O2_not_held","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":[]},"expect":{"pick":1,"skipped":[[0,"prior_not_held"]]}},
|
||||
{"name":"select_O2_png_only","op":"select","input":{"object":"O2","advertises":["wai.image.png"],"held":[]},"expect":{"pick":3,"skipped":[[0,"missing_capability"],[1,"missing_capability"],[2,"missing_capability"]]}},
|
||||
{"name":"select_O2_held","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2,3]},"expect":{"pick":0,"skipped":[]}},
|
||||
{"name":"select_O2_not_held","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":[],"order":[0,1,2,3]},"expect":{"pick":1,"skipped":[[0,"prior_not_held"]]}},
|
||||
{"name":"select_O2_default","op":"select","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"pick":1,"skipped":[]}},
|
||||
{"name":"select_O2_png_only","op":"select","input":{"object":"O2","advertises":["wai.image.png"],"held":[]},"expect":{"pick":3,"skipped":[[1,"missing_capability"],[2,"missing_capability"]]}},
|
||||
{"name":"select_O3","op":"select","input":{"object":"O3","advertises":["wai.video.av1"]},"expect":{"pick":0,"skipped":[]}},
|
||||
{"name":"select_O7","op":"select","input":{"object":"O7","advertises":["wai.video.av1"]},"expect":{"pick":1,"skipped":[[0,"component_digest_mismatch"]]}},
|
||||
{"name":"select_O3_nothing","op":"select","input":{"object":"O3","advertises":[]},"expect":{"inert":[[0,"missing_capability"],[1,"missing_capability"],[2,"not_selectable"]]}},
|
||||
|
|
@ -135,8 +136,9 @@
|
|||
{"name":"fetch_O8_deadline","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"],"tamper":{"kind":"no_refinement"}},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":4,"companions":[],"refinements":[],"abandoned":[1],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_full","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":["wai.video.lcevc"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":28844,"requests":4,"companions":[1],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O8_no_enhancement","op":"fetch","input":{"object":"O8","fragment":"#n=46433&h=1009","advertises":["wai.video.avc"],"applies":[]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":8844,"requests":3,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"e93f5c55dc5938a8cadd00477e4110d567d4a725b647a3cddf0c70c5e59dcd18"}},
|
||||
{"name":"fetch_O2_held","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"5b2bf2dbd36a683d4a62292d1a179e695c511c4971be6859cec41cd980e4f971"}},
|
||||
{"name":"fetch_O2_not_held","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":[]},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[[0,"prior_not_held"]],"component_sha256":"c56f9ab9a3b1df0bd650c3177bd85427f4a4be8fde82547219bcea4ec254162c"}},
|
||||
{"name":"fetch_O2_held","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"],"order":[0,1,2,3]},"expect":{"role":"base","pick":0,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"5b2bf2dbd36a683d4a62292d1a179e695c511c4971be6859cec41cd980e4f971"}},
|
||||
{"name":"fetch_O2_not_held","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":[],"order":[0,1,2,3]},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[[0,"prior_not_held"]],"component_sha256":"c56f9ab9a3b1df0bd650c3177bd85427f4a4be8fde82547219bcea4ec254162c"}},
|
||||
{"name":"fetch_O2_default","op":"fetch","input":{"object":"O2","advertises":["wai.neural.int_mlicpp","wai.image.avif","wai.image.jxl","wai.image.png"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"role":"base","pick":1,"verified_by":"head-proof","why_whole":null,"body_bytes":16582,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"c56f9ab9a3b1df0bd650c3177bd85427f4a4be8fde82547219bcea4ec254162c"}},
|
||||
{"name":"fetch_O1","op":"fetch","input":{"object":"O1","advertises":["wai.neural.int_mlicpp"],"held":["../wai-rs/tests/vectors/int_mlicpp/model.wmb"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"single_component","body_bytes":912,"requests":2,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"5b2bf2dbd36a683d4a62292d1a179e695c511c4971be6859cec41cd980e4f971"}},
|
||||
{"name":"fetch_O1_not_held","op":"fetch","input":{"object":"O1","advertises":["wai.neural.int_mlicpp"],"held":[]},"expect":{"error":"inert","skipped":[[0,"prior_not_held"]]}},
|
||||
{"name":"fetch_O5_small","op":"fetch","input":{"object":"O5","fragment":"#n=251","advertises":["wai.text.zstd"]},"expect":{"role":"base","pick":0,"verified_by":"whole-object","why_whole":"below_one_chunk","body_bytes":251,"requests":1,"companions":[],"refinements":[],"abandoned":[],"dropped":[],"skipped":[],"component_sha256":"591f780863bd571c1b868efabdf64a30ff0734b22582dad3f89486d0a97663c1"}},
|
||||
|
|
|
|||
|
|
@ -70,8 +70,26 @@ def blake3(b):
|
|||
return B3.blake3(b)
|
||||
|
||||
|
||||
# Each object's content hash, computed once: an object resolved many times (resolve_uri,
|
||||
# once per URI that names it) is hashed once. A caller that has checked an object's hash
|
||||
# against its own record may seed it (seed_content_hash).
|
||||
_CONTENT_HASH = {}
|
||||
|
||||
|
||||
def content_hash(env):
|
||||
return blake3(DOMAIN + env)
|
||||
h = _CONTENT_HASH.get(env)
|
||||
if h is None:
|
||||
h = _CONTENT_HASH[env] = blake3(DOMAIN + env)
|
||||
return h
|
||||
|
||||
|
||||
def seed_content_hash(env, h):
|
||||
"""Record an object's content hash that the caller has computed and checked."""
|
||||
_CONTENT_HASH[env] = h
|
||||
|
||||
|
||||
def forget_content_hashes():
|
||||
_CONTENT_HASH.clear()
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ the registry
|
||||
|
|
@ -314,6 +332,31 @@ def selectable(e):
|
|||
return e.get("role") not in ("companion", "refinement") and dispatchable(e["capability"])
|
||||
|
||||
|
||||
# SPEC §4, *Energy order*: a capability is learned unless its registry class is one of the
|
||||
# three standard classes (a string the registry does not hold is learned too). The corpus
|
||||
# states no platform answer and no measurement, so a classical entry ranks `classical` and a
|
||||
# learned one `learned-without-budget`, after it.
|
||||
STANDARD = ("standard-defined", "standard-tolerance", "standard-unclassified")
|
||||
|
||||
|
||||
def energy_rank(cap):
|
||||
c = REG.get(cap)
|
||||
return 2 if c is not None and c["replicate"] in STANDARD else 3
|
||||
|
||||
|
||||
def energy_order(entries):
|
||||
"""SPEC §7.1 step 2's default order: the selectable entries in SPEC §4's energy order
|
||||
(with no platform answer and no measurement, every classical entry ranks `classical`
|
||||
and a learned one after them, table order within each), each in one of the selectable
|
||||
entries' places; every other entry in its own place."""
|
||||
slots = [i for i, e in enumerate(entries) if selectable(e)]
|
||||
ranked = sorted(slots, key=lambda i: (energy_rank(entries[i]["capability"]), i))
|
||||
out = list(range(len(entries)))
|
||||
for s, r in zip(slots, ranked):
|
||||
out[s] = r
|
||||
return out
|
||||
|
||||
|
||||
def resolve_pin(cap, prior, held):
|
||||
if not isinstance(prior, dict) or "sha256" not in prior:
|
||||
raise SystemExit("http_verify: a pin shape this verifier does not read")
|
||||
|
|
@ -988,7 +1031,7 @@ class Session:
|
|||
self.to_rest("single_component", "base")
|
||||
else:
|
||||
entries = head["manifest"]["renditions"]
|
||||
ordr = order(head) if callable(order) else (order if order is not None else list(range(len(entries))))
|
||||
ordr = order(head) if callable(order) else (order if order is not None else energy_order(entries))
|
||||
pick, skipped = select(head["manifest"], ordr, adv, held_sets, lambda i, e: True)
|
||||
wanted = [(pick, "base")] + self.applied(entries, pick)
|
||||
nc = self.nonconforming(entries, wanted)
|
||||
|
|
@ -1085,7 +1128,7 @@ class Session:
|
|||
return
|
||||
entries = m["renditions"]
|
||||
pay = [envb[s:s + ln] for s, ln in head["components"]]
|
||||
ordr = order(head) if callable(order) else (order if order is not None else list(range(len(entries))))
|
||||
ordr = order(head) if callable(order) else (order if order is not None else energy_order(entries))
|
||||
pick, skipped = select(m, ordr, adv, held_sets, lambda i, e: payload_ok(e, pay[i]))
|
||||
comps, refs, dropped = [], [], []
|
||||
applied = self.applied(entries, pick)
|
||||
|
|
@ -3035,7 +3078,11 @@ def resolve_uri(objs, uri):
|
|||
name = base.rsplit("/", 1)[-1]
|
||||
if name.endswith(".wai") and re.fullmatch(r"[0-9a-f]{64}", name[:-4]):
|
||||
cid = name[:-4]
|
||||
b = next((b for b in objs.values() if cid is not None and content_hash(b).hex() == cid), None)
|
||||
# An object held under its own content id is found by name, its hash checked; any
|
||||
# other by its content hash, each object hashed once (content_hash).
|
||||
b = objs.get(f"{cid}.wai") if cid is not None else None
|
||||
if b is None or content_hash(b).hex() != cid:
|
||||
b = next((b for b in objs.values() if cid is not None and content_hash(b).hex() == cid), None)
|
||||
if b is None:
|
||||
raise SystemExit("http_verify: %s names no corpus object" % uri)
|
||||
return b
|
||||
|
|
@ -3339,7 +3386,7 @@ def evaluate(d, objs, op, inp):
|
|||
h = whole_head(b)
|
||||
pay = [b[s:s + ln] for s, ln in h["components"]]
|
||||
adv, _, held, order, _, _ = sink_of(d, inp, objs)
|
||||
order = order(h) if callable(order) else (order if order is not None else list(range(len(pay))))
|
||||
order = order(h) if callable(order) else (order if order is not None else energy_order(h["manifest"]["renditions"]))
|
||||
try:
|
||||
i, sk = select(h["manifest"], order, adv, held, lambda i, e: payload_ok(e, pay[i]))
|
||||
return {"pick": i, "skipped": sk}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -14,6 +14,8 @@ For every case of vectors.json it recomputes, from the text:
|
|||
|
||||
- `routes`: the report read or refused (§3), and each object's route, reason, legs,
|
||||
`asked`, tier and energy entry (§4, §6);
|
||||
- `choices`: the choice table of §4.7, each choice's alternatives in SPEC §4's energy
|
||||
order and the one chosen;
|
||||
- `constraints`: §5's outcome and reason;
|
||||
- `bindings`: §7.1's reading and canonical form; `frame_tiers`: §7.2's tier;
|
||||
- `chain`: each group receipt's signature (jwp-receipts §2.3, legacy form), its root and
|
||||
|
|
@ -270,6 +272,8 @@ def report_reads(r):
|
|||
and "codec" in o and (o["codec"] is None or isinstance(o["codec"], str))
|
||||
and isinstance(o.get("need"), str) and isinstance(o.get("params"), dict)):
|
||||
return False
|
||||
if "choice" in o and not (isinstance(o["choice"], str) and o["choice"]):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
|
@ -391,6 +395,64 @@ def route_report(r):
|
|||
|
||||
# ─────────────────────────────── §7 ───────────────────────────────
|
||||
|
||||
# SPEC §4, *Energy order*: a learned capability is one whose registry class is not one of the
|
||||
# three standard classes, or a string this revision does not register.
|
||||
STANDARD = ("standard-defined", "standard-tolerance", "standard-unclassified")
|
||||
RANKS = ["first", "smooth", "classical", "learned-without-budget"]
|
||||
|
||||
|
||||
def learned(cap):
|
||||
c = CAPS.get(cap)
|
||||
return c is None or c["replicate"] not in STANDARD
|
||||
|
||||
|
||||
def choose_report(r):
|
||||
"""§4.7: (choice table, None) or (None, reason)."""
|
||||
table, reason = route_report(r)
|
||||
if reason is not None:
|
||||
return None, reason
|
||||
groups = []
|
||||
for i, o in enumerate(r["objects"]):
|
||||
if "choice" not in o:
|
||||
continue
|
||||
for g in groups:
|
||||
if g[0] == o["choice"]:
|
||||
g[1].append(i)
|
||||
break
|
||||
else:
|
||||
groups.append((o["choice"], [i]))
|
||||
|
||||
def rank(i):
|
||||
e, o = table[i], r["objects"][i]
|
||||
if e["route"] == "none":
|
||||
return None
|
||||
cap = o["capability"]
|
||||
if learned(cap):
|
||||
# A sink-metered figure of its decode under its route's key (§6) that reads, with a
|
||||
# hardware class and coverage Whole; its value is not read.
|
||||
m = r["energy"].get("%s %s" % (cap, e["route"]))
|
||||
ok = m is not None and read_measurement(m)[1] is None and m.get("class") in ("OnChipCounter", "CalibratedInstrument") and m.get("coverage") == "Whole"
|
||||
return "classical" if ok else "learned-without-budget"
|
||||
if e["route"] == "ua":
|
||||
a = r["ua"].get(o["codec"]) or {}
|
||||
if a.get("supported") is True and a.get("smooth") is True and a.get("power_efficient") is True:
|
||||
return "first"
|
||||
if a.get("smooth") is True:
|
||||
return "smooth"
|
||||
return "classical"
|
||||
|
||||
out = []
|
||||
for choice, members in groups:
|
||||
ranked = [(rank(i), i) for i in members]
|
||||
order = sorted(ranked, key=lambda x: (RANKS.index(x[0]) if x[0] is not None else len(RANKS), x[1]))
|
||||
chosen = next((i for rk, i in order if rk is not None), None)
|
||||
out.append({"choice": choice, "order": [r["objects"][i]["name"] for _, i in order],
|
||||
"ranks": [rk for rk, _ in order],
|
||||
"chosen": None if chosen is None else r["objects"][chosen]["name"],
|
||||
"reason": None if chosen is not None else "no_alternative"})
|
||||
return out, None
|
||||
|
||||
|
||||
def hex32(s):
|
||||
return isinstance(s, str) and re.fullmatch(r"[0-9a-f]{64}", s) is not None
|
||||
|
||||
|
|
@ -579,6 +641,15 @@ def verify(doc, load):
|
|||
take(e["energy"]["reason"])
|
||||
for leg in e["legs"].values():
|
||||
take(leg)
|
||||
for c in doc["choices"]:
|
||||
got, reason = choose_report(c["report"])
|
||||
table = route_report(c["report"])[0] if reason is None else None
|
||||
want = ("refused", reason, None, None) if reason is not None else ("chosen", None, table, got)
|
||||
if want != (c["outcome"], c["reason"], c["table"], c["choices"]):
|
||||
fails.append("choice %s: %s" % (c["name"], json.dumps(got if got is not None else reason)[:300]))
|
||||
take(c["reason"])
|
||||
for g in c["choices"] or []:
|
||||
take(g["reason"])
|
||||
for c in doc["constraints"]:
|
||||
o, r = evaluate(c["sets"], c["params"])
|
||||
if (o, r) != (c["outcome"], c["reason"]):
|
||||
|
|
@ -620,7 +691,8 @@ def verify(doc, load):
|
|||
|
||||
KNOWN = ["report_malformed", "not_dispatchable", "tier_exceeds_class", "constraints_unevaluable", "constraints_unmet",
|
||||
"no_ua_mapping", "codec_mismatch", "codec_unregistered", "ua_not_asked", "ua_unsupported", "ua_inexact",
|
||||
"ua_unprobed", "ua_probe_mismatch", "no_host", "host_inexact", "no_wasm", "no_decoder", "constraint_malformed",
|
||||
"ua_unprobed", "ua_probe_mismatch", "no_host", "host_inexact", "no_wasm", "no_decoder", "no_alternative",
|
||||
"constraint_malformed",
|
||||
"parameter_unregistered", "energy_class_invalid", "unmetered_as_zero", "energy_count_invalid",
|
||||
"energy_evidence_missing", "binding_malformed", "binding_alg_unknown", "binding_unverified_reference",
|
||||
"binding_ambiguous"]
|
||||
|
|
@ -658,6 +730,17 @@ def mutations(doc):
|
|||
out.append(("powerEfficient stated off the ua route", m(lambda d: entry(d, "energy", "estimator_wasm")["energy"].update(power_efficient=True)), None))
|
||||
out.append(("powerEfficient inverted", m(lambda d: find(d, "routes", "room_tracks")["report"]["ua"]["vp09.00.10.08"].update(power_efficient=False)), None))
|
||||
out.append(("a zero read as a figure", m(lambda d: entry(d, "energy", "zero_with_class")["energy"].update(reason=None)), None))
|
||||
out.append(("a learned alternative chosen above a classical one", m(lambda d: find(d, "choices", "classical_before_learned")["choices"][0].update(order=["int_hyper", "int_motion"], chosen="int_hyper")), None))
|
||||
out.append(("the power-efficient answer withdrawn", m(lambda d: find(d, "choices", "hardware_first")["report"]["ua"]["av01.0.04M.08"].update(power_efficient=False)), None))
|
||||
out.append(("no answer read as power-efficient", m(lambda d: find(d, "choices", "power_efficient_not_stated")["report"]["ua"]["avc1.64001f"].update(power_efficient=True)), None))
|
||||
out.append(("an alternative routed none chosen", m(lambda d: find(d, "choices", "no_alternative")["choices"][0].update(chosen="hevc", reason=None)), None))
|
||||
out.append(("an empty choice read", m(lambda d: find(d, "choices", "empty_choice")["report"]["objects"][0].update(choice="clip")), None))
|
||||
out.append(("a partial figure read as qualifying", m(lambda d: find(d, "choices", "learned_with_a_partial_measurement")["report"]["energy"]["wai.video.int_hyper wasm"].update(coverage="Whole")), None))
|
||||
out.append(("a modelled figure read as qualifying", m(lambda d: find(d, "choices", "learned_with_a_modelled_figure")["choices"][0].update(order=["int_hyper", "int_motion"], ranks=["classical", "classical"], chosen="int_hyper")), None))
|
||||
out.append(("a host-routed decode ranked on the user agent's answer", m(lambda d: find(d, "choices", "power_efficient_answer_for_a_host_routed_decode")["choices"][0].update(order=["hevc", "av1"], ranks=["first", "smooth"], chosen="hevc")), None))
|
||||
out.append(("a power-efficient answer that is not smooth put first", m(lambda d: find(d, "choices", "smooth_before_power_efficient_but_not_smooth")["choices"][0].update(order=["hevc", "av1"], ranks=["first", "smooth"], chosen="hevc")), None))
|
||||
out.append(("a rank renamed", m(lambda d: find(d, "choices", "hardware_first")["choices"][0]["ranks"].__setitem__(1, "classical")), None))
|
||||
out.append(("a route of none ranked before a learned alternative", m(lambda d: find(d, "choices", "learned_when_nothing_else_routes")["choices"][0].update(order=["hevc", "int_hyper"], ranks=[None, "learned-without-budget"])), None))
|
||||
out.append(("a constraint bound moved", m(lambda d: find(d, "constraints", "bounds_inclusive")["sets"][0]["wai:frame_height"].update(minimum=1081)), None))
|
||||
out.append(("an unregistered parameter registered", m(lambda d: find(d, "constraints", "unregistered").update(outcome="unsatisfied", reason="constraints_unmet")), None))
|
||||
out.append(("a digest upper-cased", m(lambda d: find(d, "bindings", "canonical")["value"].update(digest="1F" * 32)), None))
|
||||
|
|
@ -736,7 +819,7 @@ def main():
|
|||
fails = verify(doc, loader())
|
||||
for f in fails:
|
||||
print("FAIL " + f)
|
||||
n = sum(len(doc[k]) for k in ("routes", "constraints", "bindings", "frame_tiers", "frames", "tables"))
|
||||
n = sum(len(doc[k]) for k in ("routes", "choices", "constraints", "bindings", "frame_tiers", "frames", "tables"))
|
||||
n += sum(len(g["objects"]) for g in doc["chain"]["groups"])
|
||||
print("%s every case gets the outcome the corpus states (%d cases; every reason of §11 given)" % ("PASS" if not fails else "FAIL", n))
|
||||
bad += bool(fails)
|
||||
|
|
@ -748,9 +831,13 @@ def main():
|
|||
muts = mutations(doc)
|
||||
for name, change, file_change in muts:
|
||||
d2 = copy.deepcopy(doc)
|
||||
# A mutation may change the registry export as read; each is undone after its run.
|
||||
saved = copy.deepcopy(CAPS)
|
||||
if change:
|
||||
change(d2)
|
||||
f2 = verify(d2, loader(file_change))
|
||||
CAPS.clear()
|
||||
CAPS.update(saved)
|
||||
if f2:
|
||||
caught += 1
|
||||
else:
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -21,3 +21,10 @@ What the kernel does not do yet: escalate new questions upstream (a callback hoo
|
|||
read a rail meter (the host models one; the Zephyr skeleton counts cycles), and hold the signing seed in a
|
||||
secure element. Energy constants inside the kernel (cycle energy, bits committed) are the model's projected
|
||||
values until the bench node measures them.
|
||||
|
||||
So no receipt here is a reading. The kernel writes the grade and the device the meter states
|
||||
(`settle_meter_t.how`, `.device`) and no longer fixes them to `measured` from `hardware`: the host harness
|
||||
states `projected`, from a `simulation`, by the method `model_estimated`, and the Zephyr skeleton
|
||||
`projected` on `hardware` by the same method (oer-receipts §2: a figure computed from constants is an
|
||||
`Estimator` figure, graded `projected`). The ledger's `meter_joules` is the sum of those projected figures,
|
||||
and the harness says so when it prints it.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
/* Host harness: replays a day of demand through the kernel with a modelled meter and writes an OER/2 vector file. */
|
||||
/* Host harness: replays a day of demand through the kernel with a modelled meter and writes an OER/2 vector file.
|
||||
The meter is a model of constants (a cycle count, standby and shared draws, a link energy), none measured on the
|
||||
node, so every figure is graded projected (oer-receipts §2: an Estimator figure), from a simulation, by a modelled
|
||||
method: the receipts never present the model as a reading. */
|
||||
#include "settle.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -17,12 +20,12 @@ int main(int argc, char **argv){
|
|||
now += 20 + 80 * urand(); char cls = urand() < 0.34 ? 'A' : 'B';
|
||||
double z = urand(); uint32_t id = (uint32_t)(cls == 'A' ? 1 + 2999 * z * z * z : 1 + 399 * z * z * z);
|
||||
settle_meter_t m; m.compute_j = 120000.0 * ctx.cycle_j; m.movement_j = cls == 'A' ? 2.3e-6 : 0; m.idle_alloc_j = standby_w * (now - last); m.shared_alloc_j = shared_w * (now - last); m.upstream_j = 0;
|
||||
m.latency_s = cls == 'A' ? 0.019 : 0.0017; m.method = "hardware_meter"; m.weakest_how = "projected"; m.weakest_input = "wire_w";
|
||||
m.latency_s = cls == 'A' ? 0.019 : 0.0017; m.method = "model_estimated"; m.how = "projected"; m.device = "simulation"; m.weakest_how = "projected"; m.weakest_input = "wire_w";
|
||||
settle_result_t res; if (settle_decide(&ctx, cls, id, now, &m, buf, sizeof buf, &res)) { fprintf(stderr, "decide failed\n"); return 2; }
|
||||
meter += res.joules_total; last = now;
|
||||
fprintf(f, "%s%s", i ? "," : "", buf);
|
||||
}
|
||||
static char recbuf[1 << 20]; fprintf(f, "],\"records\":"); if (settle_records_json(&ctx, recbuf, sizeof recbuf)) { fprintf(stderr, "records overflow\n"); return 3; } fputs(recbuf, f);
|
||||
fprintf(f, ",\"ledger\":{\"meter_joules\":\"%.12e\",\"unallocated_joules\":\"0.000000e+00\",\"carried_forward_joules\":\"0.000000e+00\"},\"expected\":[]}]}\n", meter);
|
||||
fclose(f); fprintf(stderr, "wrote %s: %d receipts, meter %.6f J\n", path, n_events, meter); return 0;
|
||||
fclose(f); fprintf(stderr, "wrote %s: %d receipts, modelled meter %.6f J (projected, not a reading)\n", path, n_events, meter); return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ int settle_decide(settle_ctx_t *c, char cls, uint32_t id, double now, const sett
|
|||
PUT("\"work\":{\"status\":1,\"outcome\":\"%s\",\"latency_s\":\"%s\"},", served ? "served_from_record" : "derive", dec(d1, m->latency_s));
|
||||
PUT("\"energy\":{\"joules_total\":\"%.12e\",\"boundary\":\"%s\",\"breakdown\":{\"compute_j\":\"%s\",\"movement_j\":\"%s\",\"idle_alloc_j\":\"%s\",\"shared_alloc_j\":\"%s\",\"upstream_j\":\"%s\"},\"method\":\"%s\",\"temperature_k\":\"%s\"},",
|
||||
total, c->boundary, dec(d2, m->compute_j), dec(d3, m->movement_j), dec(d4, m->idle_alloc_j), dec(d5, m->shared_alloc_j), dec(d6, m->upstream_j), m->method, dec(d7, c->temperature_k));
|
||||
PUT("\"provenance\":{\"how\":\"measured\",\"device\":\"hardware\",\"weakest_input\":{\"how\":\"%s\",\"input\":\"%s\"}},", m->weakest_how, m->weakest_input);
|
||||
PUT("\"provenance\":{\"how\":\"%s\",\"device\":\"%s\",\"weakest_input\":{\"how\":\"%s\",\"input\":\"%s\"}},", m->how, m->device, m->weakest_how, m->weakest_input);
|
||||
PUT("\"settlement\":{\"question_key\":\"sha256:%s\",\"served_from_record\":%s,\"record_id\":\"r%u\",\"record_hash\":\"%s\",\"reuse_count\":%u,\"derivation_j\":\"%s\",\"valid_until\":%lld},",
|
||||
keyhex, served ? "true" : "false", r->id, rechash, r->reuse, dec(d8, deriv_j), (long long)r->valid_until);
|
||||
PUT("\"floor\":{\"bits_committed\":%u,\"bits_delivered\":\"1.585000e+00\",\"floor_j\":\"%s\",\"decades_above_floor\":\"%.6e\"},", c->bits_committed, dec(d9, floor_j), decades);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,11 @@
|
|||
#include <stddef.h>
|
||||
#define SETTLE_RECORDS 4096
|
||||
#define SETTLE_ANSWER 24
|
||||
typedef struct { double compute_j, movement_j, idle_alloc_j, shared_alloc_j, upstream_j; double latency_s; const char *method; const char *weakest_how, *weakest_input; } settle_meter_t;
|
||||
/* The meter's figures and how they were obtained. `how` is the receipt's evidential grade
|
||||
(oer-receipts §2: projected, simulated, derived, measured) and `device` its provenance text;
|
||||
a meter states both, and the kernel writes them as given: a figure computed from constants is
|
||||
never graded measured (oer-receipts §2: an Estimator figure is projected). */
|
||||
typedef struct { double compute_j, movement_j, idle_alloc_j, shared_alloc_j, upstream_j; double latency_s; const char *method; const char *how, *device; const char *weakest_how, *weakest_input; } settle_meter_t;
|
||||
typedef struct { uint8_t key[32]; char answer[SETTLE_ANSWER]; double created, valid_until, deriv_j; uint32_t reuse, id; uint8_t used; } settle_record_t;
|
||||
typedef struct { int status; const char *outcome; char answer[SETTLE_ANSWER]; uint32_t record_id; uint32_t reuse; char receipt_hash[72]; double joules_total; } settle_result_t;
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -33,8 +33,13 @@ int main(void){
|
|||
m.movement_j = cls == 'A' ? 2.3e-6 : 0; /* wired charger link: replace with the meter's reading */
|
||||
m.idle_alloc_j = standby_w * (now_ms - last_ms) / 1000.0;
|
||||
m.shared_alloc_j = shared_w * (now_ms - last_ms) / 1000.0;
|
||||
m.upstream_j = 0; m.method = "calibrated_model"; m.weakest_how = "projected"; m.weakest_input = "standby_w";
|
||||
m.compute_j = 0; m.latency_s = 0;
|
||||
/* No rail meter is read yet: every figure is a constant or uptime times a constant, so the
|
||||
receipt is graded projected and its method is a model, never a reading (oer-receipts §2). */
|
||||
m.upstream_j = 0; m.method = "model_estimated"; m.how = "projected"; m.device = "hardware"; m.weakest_how = "projected"; m.weakest_input = "standby_w";
|
||||
/* The projected cycle count of a decision (the host harness's), until a two-pass emit puts
|
||||
this decision's own measured cycles on its receipt: a compute figure of 0 would state that
|
||||
the decision cost nothing. */
|
||||
m.compute_j = 120000.0 * ctx.cycle_j; m.latency_s = 0;
|
||||
settle_result_t res;
|
||||
int rc = settle_decide(&ctx, cls, id, now_ms / 1000.0, &m, out, sizeof out, &res);
|
||||
uint32_t cyc = cycles_now() - c0;
|
||||
|
|
|
|||
|
|
@ -57,6 +57,43 @@ packages (§6–§11), and their delivery over a simulated link (§8.9).
|
|||
written by `supplied_floor.py`. The bumper's base, layers and floor are one object
|
||||
each; the still's piece is carried over HTTP by the floor (§8.3) and over MoQ on the
|
||||
base track.
|
||||
- `packs/video-ladder-01/` — a golden package of two floor rungs over one base (§7.1):
|
||||
the fixture and base rung of `video-base-01`, so its bases are that package's objects,
|
||||
and two supplied AV1 streams, `floor/hi.obu` at the fixture's size and `floor/lo.obu`
|
||||
at half its width and height, each written by `supplied_floor.py` (`--scale lo=2`).
|
||||
Each piece has its base and one floor object per floor rung; each floor has its own
|
||||
CMAF files under its floor name (`floor/hi/`, `floor/lo/`), its own floor playlist and
|
||||
MoQ track, and the classical master, every class's master and `dash/classic.mpd` list
|
||||
both.
|
||||
- `packs/video-repack-01/` — a re-pack golden (§11.5): `video-ladder-01`'s policy under
|
||||
another asset id and its supplied floor, packed with every piece's base taken from the
|
||||
parent its file `parent` names, `video-base-01`, rather than coded. Its objects are
|
||||
`video-ladder-01`'s; its receipt names `video-base-01`'s receipt as the package it
|
||||
re-packs (parent kind `re-pack`); and each piece's base entry is the parent's, field for
|
||||
field. Both verifiers check it against its parent.
|
||||
- `packs/video-cmaf-01/` — a golden whose supplied floor is in the CMAF form (§7.7):
|
||||
`video-ladder-01`'s policy with `floor.supplied_form = "cmaf"`, and its two streams as a
|
||||
media muxer wrote them, `floor/<name>/init.mp4` and `floor/<name>/seg_<k>.m4s`, one
|
||||
segment per piece, by `supplied_cmaf.py` (ffmpeg's HLS muxer, `-c copy`). The package's
|
||||
floor files are those files, byte for byte, under their SHA-256; `floor.listing` is the
|
||||
supplied floor's listing; and each floor object holds its piece's samples, which are the
|
||||
OBU form's temporal units of the same streams.
|
||||
- `cmaf-mutations/` — the CMAF golden altered, in `package-mutations/`'s layout: itself
|
||||
(`ok`); a segment replaced by one of the same samples, a `free` box before it, under its
|
||||
own SHA-256; `floor.listing` altered; and without it (each `floor-cmaf-mismatch`).
|
||||
- `packs/video-floor-only-01/` and `packs/video-floor-only-02/` — floor-only goldens
|
||||
(§7.8): a component of `profile = "floor-only"` with the ladder's two floor rungs and no
|
||||
base or source, the floor in the CMAF form (`video-cmaf-01`'s files) and in the OBU form
|
||||
(`video-ladder-01`'s streams). Their pieces are the first floor's segments or shown key
|
||||
frames, the ladder golden's plan; each piece is its two floor objects, which carry no
|
||||
`derived_from` and hold the ladder golden's units, of kind `none`; no class lists a WAI
|
||||
object.
|
||||
- `ladder-mutations/` — packages a verifier must refuse or accept (§11.3), each the
|
||||
ladder golden altered (re-sealed with its key where the receipt or `LISTING` alone
|
||||
would catch it), in `package-mutations/`'s layout: the golden itself (`ok`); piece 2's
|
||||
two floor objects swapped (`floor-object-mismatch`); the second floor's CMAF files
|
||||
under the component's name (`floor-cmaf-mismatch`); and a classical master listing one
|
||||
floor of two (`catalog-mismatch`).
|
||||
- `mutations/` — stage indexes, objects and version-2 receipts, and `expected.json`:
|
||||
each case's reason (§6.2–§6.4, §9.1), a well-formed case of each kind among them,
|
||||
and three receipts carrying a signature only a verifier that checks the equation
|
||||
|
|
@ -86,7 +123,9 @@ packages (§6–§11), and their delivery over a simulated link (§8.9).
|
|||
window past 32 KiB, bytes after the zlib stream, and its Adler-32. And one dynamic
|
||||
block of literals twice: with a lone distance code of one bit, read (the one
|
||||
incomplete code set the zlib library takes), and of two bits, refused.
|
||||
- `staged-mutations/` — the staged golden altered and re-sealed with its key, in the
|
||||
- `staged-mutations/` — the staged golden itself (`ok`: verified without its sources,
|
||||
every layer's prefix digest held to it), and the staged golden altered and re-sealed
|
||||
with its key, in the
|
||||
layout of `package-mutations/`: a refinement on another deadline than its plan's, a
|
||||
piece missing its top layer, a still where the policy's rule now gives the piece the
|
||||
default ladder (each `index-policy-mismatch`), a top layer coded again at another step
|
||||
|
|
@ -115,7 +154,10 @@ packages (§6–§11), and their delivery over a simulated link (§8.9).
|
|||
off among them), a missing or corrupted object, a live window no catalog can be
|
||||
written for, floor objects that do not open on a key frame or are not §7.2's, files
|
||||
and directories a strict read finds, and a receipt file that is not its canonical
|
||||
form; and one the verifier must accept (an extra key frame the policy allows).
|
||||
form; and those the verifier must accept (an extra key frame the policy allows, and a
|
||||
metered package, §9.4: the golden re-sealed with a 500 mW model meter over one second
|
||||
and its measurement claim, every member fixed), with a claim about another figure
|
||||
(`measurement-claim-invalid`) and none (`measurement-claim-missing`).
|
||||
|
||||
The fixtures are not committed. `wai-pack fixture gen <name>` writes each from its
|
||||
definition (`wai-pack/src/fixture.rs`), every byte an integer function of the frame
|
||||
|
|
|
|||
30
wai/pack-conformance/cmaf-mutations/expected.json
Normal file
30
wai/pack-conformance/cmaf-mutations/expected.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"pack": "video-cmaf-01",
|
||||
"cases": [
|
||||
{
|
||||
"name": "ok",
|
||||
"reason": "ok",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "segment-not-the-supplied-one",
|
||||
"reason": "floor-cmaf-mismatch",
|
||||
"remove": [
|
||||
"floor/hi/14cdbe56c8f57b1427ab9c2a9ebf6669ce8edec06fd7cd0961a01d2aaf720889.m4s",
|
||||
"hls/classic/floor-hi.m3u8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "floor-listing-altered",
|
||||
"reason": "floor-cmaf-mismatch",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "floor-listing-missing",
|
||||
"reason": "floor-cmaf-mismatch",
|
||||
"remove": [
|
||||
"floor.listing"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
688cb303ccb16fcdd21c8cb681f936ba813829e71fbf15df0ccb28b04c134447 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
4e4d6d55b352c17da028bcbf8d314cdd1f9bb1406367008710788665727dcffa floor.listing
|
||||
14cdbe56c8f57b1427ab9c2a9ebf6669ce8edec06fd7cd0961a01d2aaf720889 floor/hi/14cdbe56c8f57b1427ab9c2a9ebf6669ce8edec06fd7cd0961a01d2aaf720889.m4s
|
||||
759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1 floor/hi/759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1.m4s
|
||||
d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d floor/hi/d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d.m4s
|
||||
e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e floor/hi/e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e.m4s
|
||||
eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8 floor/hi/eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8.m4s
|
||||
a8a19ef9cd4d540037ef298097bbc5317de6a99690c8c4a745cc867c034208d6 floor/hi/init.mp4
|
||||
0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af floor/lo/0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af.m4s
|
||||
10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8 floor/lo/10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8.m4s
|
||||
2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73 floor/lo/2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73.m4s
|
||||
a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8 floor/lo/a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8.m4s
|
||||
c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc floor/lo/c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc.m4s
|
||||
6dd501e27365ce9ef90576771aad73de4d200362e6974551847afc3c90604344 floor/lo/init.mp4
|
||||
ebdc7e40aecbe8b61124ac78f233b0dfaf2a8c5674d3fc9bb023935de1d9b6cb hls/classic/floor-hi.m3u8
|
||||
66f5eb8b89b80e7da839b23f39665eb85480c6a725fa29d93556efee673c1b9e hls/classic/floor-lo.m3u8
|
||||
d793737b69231eface4951001e65b3c52cba864f231246a41e64d49c77ebdc97 hls/classic/master.m3u8
|
||||
d793737b69231eface4951001e65b3c52cba864f231246a41e64d49c77ebdc97 hls/legacy/master.m3u8
|
||||
8c3b5e2b18192b383c8df8675acd435b861a2585bc6eaf0c09292754f44a8cef hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
8c3b5e2b18192b383c8df8675acd435b861a2585bc6eaf0c09292754f44a8cef hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
649330bcfaaee54f802f6b4ddf6e459daca8bb0e8df86c87a9d6bd0d65cd09ea index-v.wsix
|
||||
68f00abff5ef19f074d510f8cce4592afc2a1d4981a87663965a46dc8bcbe1d3 moq/catalog.json
|
||||
9f381e2ab764b5e14770b00503c1c90b9c22ba22f87464b2869a70644af11939 objects/08797901ff55d0bb4afba5cffe95df64b7e216f3a0c9ba82006133bb9b6d7f16.wai
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
d5f9cdd996920e07339b79093b9e4e5cb4237f1780f268220cc6f89c79aea90a objects/399048a078b0f2983149266a44eaf207cfa63f490212b69389fba4d72f62c60f.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
d03f0813c266294f84483cfe4db177d094c06b95ab3a8471d6b2ec53401f05a7 objects/a236e9070d10fd09342cafdbf6973edd35e5266cf850fe2f38f77536ac803912.wai
|
||||
2316616549f7f92dc29e34fe656c332c9f4a1e20f4be26396976407acc792ba4 objects/a3ae43406f9966487b6c5f5a4d56e9dca50c327423fcfd2a98f4c342d1dbfaf9.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
6d665f1bef3784cbb428abc28724f74fa83c8f2bc812828621c93c5514b6dc0e objects/c5253efe2949669bb9df01d9f60c01f2570ba4be1c0af26105e092dfccf90d30.wai
|
||||
0acecba00acc28e313805189fd826ad3a9fab1fe69727ef2d5999fc543426289 objects/c81171fa53998cfe94207ad66ab9b53cfbd4b3f058730e62512ca9f0f6ef2ea3.wai
|
||||
f3bd9cbe3c2518c997a4d1d3a32145a04f55ef53e0b5760a6ce6aa5e14fe06a7 objects/dea0f6415f639fca99d91a0341aba164078a1bd4780e21d72cbf2234928272ec.wai
|
||||
d22c4f8ac8a5de43bbec6be04ea8b6da72f2bd133f8f57a31607362670178c68 objects/f467872fe8d3cd1f60f832c997cc14fd00abbf601c61286a612089b4a53a195a.wai
|
||||
f1f4e32ba9f76e36431d97e0fd52b9025075f553bf6b9f63c484d60573c8ea90 package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
428e24f9e1c62fc5590ce3d3a6239a5c274dcfc1391a2ea2abe8dc0562da7d20 policy.json
|
||||
9d2b4c15ac17ee074f646d0b1ed11ef56557f633ff67a3e6a6d0eb4dd1596548 policy.toml
|
||||
2f5807b891b8eb0a44b76c4303487be1dcf19e111f06f6c642e14b81c85d7c4d stages/legacy.json
|
||||
8695fff48ca86321c5198e3a8cf660ccba208df4f40fd1907e76c64a52674cf5 stages/mobile.json
|
||||
6ae56077cfc368a59489233eeee6ee7fd97437821822637ac62096ab93c85286 stages/tv.json
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
a8a19ef9cd4d540037ef298097bbc5317de6a99690c8c4a745cc867c034208d6 hi/init.mp4
|
||||
14cdbe56c8f57b1427ab9c2a9ebf6669ce8edec06fd7cd0961a01d2aaf720889 hi/seg_0000.m4s
|
||||
e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e hi/seg_0001.m4s
|
||||
d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d hi/seg_0002.m4s
|
||||
eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8 hi/seg_0003.m4s
|
||||
759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1 hi/seg_0004.m4s
|
||||
6dd501e27365ce9ef90576771aad73de4d200362e6974551847afc3c90604344 lo/init.mp4
|
||||
0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af lo/seg_0000.m4s
|
||||
c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc lo/seg_0001.m4s
|
||||
10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8 lo/seg_0002.m4s
|
||||
a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8 lo/seg_0003.m4s
|
||||
20484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73 lo/seg_0004.m4s
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
688cb303ccb16fcdd21c8cb681f936ba813829e71fbf15df0ccb28b04c134447 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
14cdbe56c8f57b1427ab9c2a9ebf6669ce8edec06fd7cd0961a01d2aaf720889 floor/hi/14cdbe56c8f57b1427ab9c2a9ebf6669ce8edec06fd7cd0961a01d2aaf720889.m4s
|
||||
759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1 floor/hi/759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1.m4s
|
||||
d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d floor/hi/d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d.m4s
|
||||
e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e floor/hi/e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e.m4s
|
||||
eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8 floor/hi/eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8.m4s
|
||||
a8a19ef9cd4d540037ef298097bbc5317de6a99690c8c4a745cc867c034208d6 floor/hi/init.mp4
|
||||
0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af floor/lo/0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af.m4s
|
||||
10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8 floor/lo/10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8.m4s
|
||||
2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73 floor/lo/2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73.m4s
|
||||
a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8 floor/lo/a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8.m4s
|
||||
c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc floor/lo/c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc.m4s
|
||||
6dd501e27365ce9ef90576771aad73de4d200362e6974551847afc3c90604344 floor/lo/init.mp4
|
||||
ebdc7e40aecbe8b61124ac78f233b0dfaf2a8c5674d3fc9bb023935de1d9b6cb hls/classic/floor-hi.m3u8
|
||||
66f5eb8b89b80e7da839b23f39665eb85480c6a725fa29d93556efee673c1b9e hls/classic/floor-lo.m3u8
|
||||
d793737b69231eface4951001e65b3c52cba864f231246a41e64d49c77ebdc97 hls/classic/master.m3u8
|
||||
d793737b69231eface4951001e65b3c52cba864f231246a41e64d49c77ebdc97 hls/legacy/master.m3u8
|
||||
8c3b5e2b18192b383c8df8675acd435b861a2585bc6eaf0c09292754f44a8cef hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
8c3b5e2b18192b383c8df8675acd435b861a2585bc6eaf0c09292754f44a8cef hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
649330bcfaaee54f802f6b4ddf6e459daca8bb0e8df86c87a9d6bd0d65cd09ea index-v.wsix
|
||||
68f00abff5ef19f074d510f8cce4592afc2a1d4981a87663965a46dc8bcbe1d3 moq/catalog.json
|
||||
9f381e2ab764b5e14770b00503c1c90b9c22ba22f87464b2869a70644af11939 objects/08797901ff55d0bb4afba5cffe95df64b7e216f3a0c9ba82006133bb9b6d7f16.wai
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
d5f9cdd996920e07339b79093b9e4e5cb4237f1780f268220cc6f89c79aea90a objects/399048a078b0f2983149266a44eaf207cfa63f490212b69389fba4d72f62c60f.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
d03f0813c266294f84483cfe4db177d094c06b95ab3a8471d6b2ec53401f05a7 objects/a236e9070d10fd09342cafdbf6973edd35e5266cf850fe2f38f77536ac803912.wai
|
||||
2316616549f7f92dc29e34fe656c332c9f4a1e20f4be26396976407acc792ba4 objects/a3ae43406f9966487b6c5f5a4d56e9dca50c327423fcfd2a98f4c342d1dbfaf9.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
6d665f1bef3784cbb428abc28724f74fa83c8f2bc812828621c93c5514b6dc0e objects/c5253efe2949669bb9df01d9f60c01f2570ba4be1c0af26105e092dfccf90d30.wai
|
||||
0acecba00acc28e313805189fd826ad3a9fab1fe69727ef2d5999fc543426289 objects/c81171fa53998cfe94207ad66ab9b53cfbd4b3f058730e62512ca9f0f6ef2ea3.wai
|
||||
f3bd9cbe3c2518c997a4d1d3a32145a04f55ef53e0b5760a6ce6aa5e14fe06a7 objects/dea0f6415f639fca99d91a0341aba164078a1bd4780e21d72cbf2234928272ec.wai
|
||||
d22c4f8ac8a5de43bbec6be04ea8b6da72f2bd133f8f57a31607362670178c68 objects/f467872fe8d3cd1f60f832c997cc14fd00abbf601c61286a612089b4a53a195a.wai
|
||||
f1f4e32ba9f76e36431d97e0fd52b9025075f553bf6b9f63c484d60573c8ea90 package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
428e24f9e1c62fc5590ce3d3a6239a5c274dcfc1391a2ea2abe8dc0562da7d20 policy.json
|
||||
9d2b4c15ac17ee074f646d0b1ed11ef56557f633ff67a3e6a6d0eb4dd1596548 policy.toml
|
||||
2f5807b891b8eb0a44b76c4303487be1dcf19e111f06f6c642e14b81c85d7c4d stages/legacy.json
|
||||
8695fff48ca86321c5198e3a8cf660ccba208df4f40fd1907e76c64a52674cf5 stages/mobile.json
|
||||
6ae56077cfc368a59489233eeee6ee7fd97437821822637ac62096ab93c85286 stages/tv.json
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
489923f27fa22f927813026d34acb69177b5eb042a513e8855b6bfaca1c80a1a dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
03ab06e026880875581952f91232405077be9a522dbf739b6a9d093458369710 floor.listing
|
||||
103573aca98afc526a45518dc6b93d0fcd58511bb34550b2f4d7b29431df9ad9 floor/hi/103573aca98afc526a45518dc6b93d0fcd58511bb34550b2f4d7b29431df9ad9.m4s
|
||||
759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1 floor/hi/759f476ce9e9b003d7d797060f9c039141d261dd65212035e96bb157783423c1.m4s
|
||||
d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d floor/hi/d3616b6368fc800ce421a492fbd0f6263b9e06ee0af1e8269ce4f20d5cb3653d.m4s
|
||||
e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e floor/hi/e5583297b1456e46366fe3317784fadf8c165c5e16e864629b194dc46290387e.m4s
|
||||
eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8 floor/hi/eb559df9571de1869981ca82672abbabad528b294b15a1d7bf9b635761c584f8.m4s
|
||||
a8a19ef9cd4d540037ef298097bbc5317de6a99690c8c4a745cc867c034208d6 floor/hi/init.mp4
|
||||
0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af floor/lo/0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af.m4s
|
||||
10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8 floor/lo/10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8.m4s
|
||||
2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73 floor/lo/2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73.m4s
|
||||
a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8 floor/lo/a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8.m4s
|
||||
c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc floor/lo/c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc.m4s
|
||||
6dd501e27365ce9ef90576771aad73de4d200362e6974551847afc3c90604344 floor/lo/init.mp4
|
||||
66f5eb8b89b80e7da839b23f39665eb85480c6a725fa29d93556efee673c1b9e hls/classic/floor-lo.m3u8
|
||||
9329fd005fc0e4926a3bc3d19f35b67fb6990c916b8a39cde128d8cf8b3dcf25 hls/classic/master.m3u8
|
||||
9329fd005fc0e4926a3bc3d19f35b67fb6990c916b8a39cde128d8cf8b3dcf25 hls/legacy/master.m3u8
|
||||
b650f6ffe5e31f29507e563fe104d5d45bf063fe553021047c35a6e0e35e9333 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
b650f6ffe5e31f29507e563fe104d5d45bf063fe553021047c35a6e0e35e9333 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
649330bcfaaee54f802f6b4ddf6e459daca8bb0e8df86c87a9d6bd0d65cd09ea index-v.wsix
|
||||
f56f7b8a585c4e85c4382eb163d8d6639284ddb2e7f2799715fa63120e7c0036 moq/catalog.json
|
||||
9f381e2ab764b5e14770b00503c1c90b9c22ba22f87464b2869a70644af11939 objects/08797901ff55d0bb4afba5cffe95df64b7e216f3a0c9ba82006133bb9b6d7f16.wai
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
d5f9cdd996920e07339b79093b9e4e5cb4237f1780f268220cc6f89c79aea90a objects/399048a078b0f2983149266a44eaf207cfa63f490212b69389fba4d72f62c60f.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
d03f0813c266294f84483cfe4db177d094c06b95ab3a8471d6b2ec53401f05a7 objects/a236e9070d10fd09342cafdbf6973edd35e5266cf850fe2f38f77536ac803912.wai
|
||||
2316616549f7f92dc29e34fe656c332c9f4a1e20f4be26396976407acc792ba4 objects/a3ae43406f9966487b6c5f5a4d56e9dca50c327423fcfd2a98f4c342d1dbfaf9.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
6d665f1bef3784cbb428abc28724f74fa83c8f2bc812828621c93c5514b6dc0e objects/c5253efe2949669bb9df01d9f60c01f2570ba4be1c0af26105e092dfccf90d30.wai
|
||||
0acecba00acc28e313805189fd826ad3a9fab1fe69727ef2d5999fc543426289 objects/c81171fa53998cfe94207ad66ab9b53cfbd4b3f058730e62512ca9f0f6ef2ea3.wai
|
||||
f3bd9cbe3c2518c997a4d1d3a32145a04f55ef53e0b5760a6ce6aa5e14fe06a7 objects/dea0f6415f639fca99d91a0341aba164078a1bd4780e21d72cbf2234928272ec.wai
|
||||
d22c4f8ac8a5de43bbec6be04ea8b6da72f2bd133f8f57a31607362670178c68 objects/f467872fe8d3cd1f60f832c997cc14fd00abbf601c61286a612089b4a53a195a.wai
|
||||
f1f4e32ba9f76e36431d97e0fd52b9025075f553bf6b9f63c484d60573c8ea90 package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
428e24f9e1c62fc5590ce3d3a6239a5c274dcfc1391a2ea2abe8dc0562da7d20 policy.json
|
||||
9d2b4c15ac17ee074f646d0b1ed11ef56557f633ff67a3e6a6d0eb4dd1596548 policy.toml
|
||||
2f5807b891b8eb0a44b76c4303487be1dcf19e111f06f6c642e14b81c85d7c4d stages/legacy.json
|
||||
8695fff48ca86321c5198e3a8cf660ccba208df4f40fd1907e76c64a52674cf5 stages/mobile.json
|
||||
6ae56077cfc368a59489233eeee6ee7fd97437821822637ac62096ab93c85286 stages/tv.json
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" type="static" mediaPresentationDuration="PT8.000S" minBufferTime="PT3S">
|
||||
<Period id="p0" start="PT0S">
|
||||
<AdaptationSet id="1" contentType="video" mimeType="video/mp4" codecs="av01.0.00M.08" width="32" height="18" segmentAlignment="true" startWithSAP="1">
|
||||
<Representation id="lo-floor" bandwidth="8152">
|
||||
<SegmentList timescale="25">
|
||||
<Initialization sourceURL="../floor/lo/init.mp4"/>
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="60"/>
|
||||
<S t="60" d="25"/>
|
||||
<S t="85" d="50"/>
|
||||
<S t="135" d="25"/>
|
||||
<S t="160" d="40"/>
|
||||
</SegmentTimeline>
|
||||
<SegmentURL media="../floor/lo/0e0407f5a552e405846a411627df17537e5c31accca37390c391a7c1d27353af.m4s"/>
|
||||
<SegmentURL media="../floor/lo/c29687008dafdf251b2ca962c3e830f785b38a56c81aee1f4c86f2e8f9115fcc.m4s"/>
|
||||
<SegmentURL media="../floor/lo/10b19654062fa37b83c082c922f54529725c36a4ead953b1fbfbbee14d4e79a8.m4s"/>
|
||||
<SegmentURL media="../floor/lo/a855b2701af4e7c8c912448be9fc023f911b4e9e94b65770393c4df6d705b5e8.m4s"/>
|
||||
<SegmentURL media="../floor/lo/2c484c9bead2b261ac1a18c2aedec11761813f995d11dc9779c5f9b039d59b73.m4s"/>
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=8152,AVERAGE-BANDWIDTH=7583,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=8152,AVERAGE-BANDWIDTH=7583,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=8152,AVERAGE-BANDWIDTH=7583,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=8152,AVERAGE-BANDWIDTH=7583,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"tracks":[{"altGroup":1,"isLive":false,"name":"v/l0","namespace":"video-cmaf-01","packaging":"wai","renderGroup":1,"role":"video","wai_capabilities":["wai.video.int_motion"],"wai_determinism":"decode-equivalence","wai_receipt_alias":2923025889890445,"wai_receipts":true,"wai_receipts_track":"v/l0/receipts","wai_staged":{"layer":0,"lead_ms":null,"set":"v"}},{"altGroup":1,"codec":"av01.0.00M.08","height":18,"name":"lo/floor","namespace":"video-cmaf-01","packaging":"cmaf","renderGroup":1,"width":32},{"isLive":false,"name":"v/l0/receipts","namespace":"video-cmaf-01","packaging":"wai-receipts"}],"version":1}
|
||||
148
wai/pack-conformance/cmaf_diff.py
Normal file
148
wai/pack-conformance/cmaf_diff.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#!/usr/bin/env python3
|
||||
"""The supplied CMAF reader differentially (extensions/packaging.md §7.7): wai-pack's
|
||||
(`wai-pack cmaf-verdict`) against pack_verify.py's, which shares no code with it, on
|
||||
seeded alterations of real files. The files are the CMAF golden's floor (a media muxer's
|
||||
initialization and media segments, packs/video-cmaf-01/floor/) and §7.6's writer's (the
|
||||
ladder golden's package, packs/video-ladder-01/package/floor/). Each case alters the
|
||||
initialization segment or one media segment: a byte flipped in its first boxes, a 32-bit
|
||||
field overwritten at a box boundary, the file cut short, a box's size changed, a box
|
||||
inserted at the top level or in a moof, the init's timescale or edit list altered, or a
|
||||
segment's moof and mdat repeated. Every verdict (ok, floor-cmaf-malformed or
|
||||
floor-cmaf-timing), and for an accepted segment its samples (offset, length, duration,
|
||||
flags, decode time), must agree, and no reader may panic.
|
||||
|
||||
python3 cmaf_diff.py <wai-pack binary> <seed> <cases>
|
||||
"""
|
||||
import hashlib
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, HERE)
|
||||
import pack_verify as PV # noqa: E402
|
||||
|
||||
|
||||
def py_init(b):
|
||||
try:
|
||||
return "ok", PV.cmaf_init(b)
|
||||
except PV.CmafFault as x:
|
||||
return x.code, None
|
||||
except Exception as x: # a reader must refuse, never fail otherwise
|
||||
return f"CRASH {type(x).__name__}", None
|
||||
|
||||
|
||||
def py_segment(b, init):
|
||||
try:
|
||||
s = PV.cmaf_segment(b, init)
|
||||
except PV.CmafFault as x:
|
||||
return (x.code, 0, "-")
|
||||
except Exception as x:
|
||||
return (f"CRASH {type(x).__name__}", 0, "-")
|
||||
text = "".join(f"{a},{n},{d},{f},{t};" for a, n, d, f, t in s)
|
||||
return ("ok", len(s), hashlib.sha256(text.encode()).hexdigest())
|
||||
|
||||
|
||||
def boxes(b, start, end):
|
||||
try:
|
||||
return PV.bmff_boxes(b, start, end)
|
||||
except PV.CmafFault:
|
||||
return []
|
||||
|
||||
|
||||
def alter(rng, b):
|
||||
b = bytearray(b)
|
||||
top = boxes(b, 0, len(b))
|
||||
k = rng.randrange(8)
|
||||
if k == 6:
|
||||
# The times: an init's timescale or its edit list's media time, at their offsets.
|
||||
for kind, off in ((b"mdhd", 20), (b"elst", 20), (b"elst", 16)):
|
||||
at = bytes(b).find(kind)
|
||||
if at >= 0 and rng.random() < 0.5 and at + off + 4 <= len(b):
|
||||
b[at + off:at + off + 4] = rng.choice([0, 1, 2, rng.randrange(1 << 32)]).to_bytes(4, "big")
|
||||
return bytes(b)
|
||||
k = 0
|
||||
if k == 7:
|
||||
# A media segment's moof and mdat repeated: the second's decode time is the first's.
|
||||
pairs = [i for i, x in enumerate(top) if x[0] == b"moof" and i + 1 < len(top) and top[i + 1][0] == b"mdat"]
|
||||
if pairs:
|
||||
i = rng.choice(pairs)
|
||||
b += b[top[i][1]:top[i + 1][3]]
|
||||
return bytes(b)
|
||||
k = 0
|
||||
if k == 0 and b:
|
||||
b[rng.randrange(min(len(b), 400))] ^= 1 << rng.randrange(8)
|
||||
elif k == 1 and top:
|
||||
x = rng.choice(top)
|
||||
inner = boxes(b, x[2], x[3]) if x[0] in (b"moov", b"moof", b"trak", b"traf", b"mdia", b"minf", b"stbl", b"mvex") else []
|
||||
y = rng.choice(inner) if inner and rng.random() < 0.7 else x
|
||||
at = y[1] + rng.choice([8, 12, 16, 20, 24])
|
||||
if at + 4 <= len(b):
|
||||
b[at:at + 4] = rng.choice([0, 1, 7, 0xFFFFFFFF, rng.randrange(1 << 32)]).to_bytes(4, "big")
|
||||
elif k == 2 and len(b) > 8:
|
||||
del b[rng.randrange(8, len(b)):]
|
||||
elif k == 3 and top:
|
||||
x = rng.choice(top)
|
||||
b[x[1]:x[1] + 4] = max(0, (x[3] - x[1]) + rng.choice([-9, -1, 1, 4, 8, -(x[3] - x[1])])).to_bytes(4, "big")
|
||||
elif k == 4 and top:
|
||||
kind = rng.choice([b"free", b"skip", b"uuid", b"styp", b"mdat", b"moof"])
|
||||
box = (8 + 4).to_bytes(4, "big") + kind + bytes(4)
|
||||
at = rng.choice([x[1] for x in top] + [len(b)])
|
||||
b[at:at] = box
|
||||
elif top:
|
||||
moofs = [x for x in top if x[0] == b"moof"]
|
||||
if moofs:
|
||||
x = rng.choice(moofs)
|
||||
inner = boxes(b, x[2], x[3])
|
||||
at = rng.choice([y[1] for y in inner] + [x[3]])
|
||||
kind = rng.choice([b"sbgp", b"free", b"tfdt", b"trun"])
|
||||
box = (8 + 8).to_bytes(4, "big") + kind + bytes(8)
|
||||
b[at:at] = box
|
||||
b[x[1]:x[1] + 4] = (x[3] - x[1] + len(box)).to_bytes(4, "big")
|
||||
return bytes(b)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 4:
|
||||
print(__doc__)
|
||||
return 2
|
||||
wai_pack, seed, n = argv[1], int(argv[2]), int(argv[3])
|
||||
rng = random.Random(seed)
|
||||
root = os.path.join(HERE, "packs")
|
||||
sets = []
|
||||
for d in (os.path.join(root, "video-cmaf-01", "floor", "hi"), os.path.join(root, "video-cmaf-01", "floor", "lo"),
|
||||
os.path.join(root, "video-ladder-01", "package", "floor", "hi")):
|
||||
names = sorted(os.listdir(d))
|
||||
sets.append((PV.load(os.path.join(d, "init.mp4")), [PV.load(os.path.join(d, x)) for x in names if x.endswith(".m4s")]))
|
||||
bad, seen = 0, {}
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
for c in range(n):
|
||||
init, segs = rng.choice(sets)
|
||||
seg = rng.choice(segs)
|
||||
if rng.random() < 0.25:
|
||||
init = alter(rng, init)
|
||||
else:
|
||||
seg = alter(rng, seg)
|
||||
ip, sp = os.path.join(tmp, "init.mp4"), os.path.join(tmp, "seg.m4s")
|
||||
open(ip, "wb").write(init)
|
||||
open(sp, "wb").write(seg)
|
||||
out = subprocess.run([wai_pack, "cmaf-verdict", ip, sp], capture_output=True, text=True).stdout.splitlines()
|
||||
iv, pi = py_init(init)
|
||||
want = [f"init\t{iv}"]
|
||||
if iv == "ok":
|
||||
v = py_segment(seg, pi)
|
||||
want.append(f"{sp}\t{v[0]}\t{v[1]}\t{v[2]}")
|
||||
verdict = want[-1].split("\t")[1]
|
||||
seen[verdict] = seen.get(verdict, 0) + 1
|
||||
if out != want or any("PANIC" in x or "CRASH" in x for x in out + want):
|
||||
bad += 1
|
||||
if bad <= 10:
|
||||
print(f"case {c}: wai-pack {out}, pack_verify {want}")
|
||||
print(f"cmaf_diff: {n - bad}/{n} cases agree; verdicts {dict(sorted(seen.items()))}")
|
||||
return 0 if bad == 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
35
wai/pack-conformance/ladder-mutations/expected.json
Normal file
35
wai/pack-conformance/ladder-mutations/expected.json
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"pack": "video-ladder-01",
|
||||
"cases": [
|
||||
{
|
||||
"name": "ok",
|
||||
"reason": "ok",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "floors-swapped",
|
||||
"reason": "floor-object-mismatch",
|
||||
"remove": [
|
||||
"floor/hi/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s",
|
||||
"floor/lo/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "floor-under-the-component-s-name",
|
||||
"reason": "floor-cmaf-mismatch",
|
||||
"remove": [
|
||||
"floor/lo/589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f.m4s",
|
||||
"floor/lo/6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db.m4s",
|
||||
"floor/lo/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s",
|
||||
"floor/lo/d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833.m4s",
|
||||
"floor/lo/d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9.m4s",
|
||||
"floor/lo/init.mp4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "master-lists-one-floor",
|
||||
"reason": "catalog-mismatch",
|
||||
"remove": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
c5203585fea0962de941d258789157a0590e18b62a2e5d1facbd11e1cfd1ddb9 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6 floor/hi/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf floor/hi/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b floor/hi/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a floor/hi/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc floor/hi/576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc.m4s
|
||||
58c598174eed554cd0d1bc8abedca8c9d51d992c8e3b963bcb7574da4983ed9f floor/hi/init.mp4
|
||||
589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f floor/v/589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f.m4s
|
||||
6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db floor/v/6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db.m4s
|
||||
acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74 floor/v/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s
|
||||
d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833 floor/v/d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833.m4s
|
||||
d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9 floor/v/d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9.m4s
|
||||
640cd81c83baefbe13e8ccc2bf577395c6602885cc73e201ca34dc267e7f0a48 floor/v/init.mp4
|
||||
bfa98255d682517f58df4073bc71fdb3fcca120fc33d16e4aca873392476c586 hls/classic/floor-hi.m3u8
|
||||
ad81674ac359096ebed255a26a40e82858e53e9754702429e59d72898df33812 hls/classic/floor-lo.m3u8
|
||||
3fd88f9dcf835edf0fcca8dff8371d2b4e897ac097fc85901477c950c0da7996 hls/classic/master.m3u8
|
||||
3fd88f9dcf835edf0fcca8dff8371d2b4e897ac097fc85901477c950c0da7996 hls/legacy/master.m3u8
|
||||
09e75f55cb7ea829a4e630404d2ab540e8be0dbb1c4365dc30802359b658aed9 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
09e75f55cb7ea829a4e630404d2ab540e8be0dbb1c4365dc30802359b658aed9 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
a5140df0bf1f82b88335c13789502298a952b350ad45dfcfcd249b2b9a263a71 index-v.wsix
|
||||
c310c5a4ae14df691a1a37a6c34e90243ed72be1e8f725d38f1babe1f6866b21 moq/catalog.json
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
2f5bd830d841eb122c63ae57a15442075478fd522ce55b6f9bcbe41acd51d8ae objects/482fbbb9b39bdb9cbf00515f94269ea591df681ced90b05d97e0df106b49b08e.wai
|
||||
0057633252a2c2ed5f71fe14ca78075c8e89fc6675a08335ac90f0e56032b825 objects/4ab8f747d181632f342a4887b0a8bb86c174409cb625157784e18c5e5559a381.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
1a6f30fdc000f258c948470386d84beef2ba5a84e922c355093837ae21fd573c objects/6ce13c9ef6872caeb7fbe71661ca88bcc463ceac47f109c8c155e34bb1a6f37d.wai
|
||||
b773dac63c9d2d9980d0d249ea956e03065c1ae40d17c512df29b690c0c820ef objects/b7bf0717e968d7b6be473785c9f66cbb814cde679bceb1ae37f0765308ddee72.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
cc8a6fc59be29bd33d96fb30ea9e5a30a5aa367a82da9c5784cbadce63396f62 objects/cf842ef3058d3c54d56c0ea3e075efdc77163602b4a1dabcff080e5ea28b9946.wai
|
||||
01b596b672de3352adb3e01b34fcddfeff975f4ee1d0b88dde160ac10c9bf929 objects/d382ba093b0595c44a43dc3aad22fc0bed42baeb3488f00dfe3567056a7009c5.wai
|
||||
9d9ae296558ce7f7d644ad0ca3af65eb4fc98d03b0bd6d7a8fc821fc952e86c1 objects/f525b4450c29153dde0b43e4d143dfb76fcc3884c607b744c8a53fa6a1728580.wai
|
||||
3d573ee482f8187b0e3bc839aeea0f51a67144588d6d207e8466fea91a0c5b34 objects/f7f7f4d6af777bfcce09cdda5ecb754ce6bd8dcd2e03ecfc41359cad989b15f7.wai
|
||||
bd6086051d0452449da8a90f426f454fee368b4794f7c294218a410ded8d9088 package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
d1cf1bd28a06f3a9d695dd64331cb56cf793818e9e9e6cec620183d9dce37b2d policy.json
|
||||
4ba48d04e5c8e72336e32307b27b4371c96f20f7abf959ac1c1d344ccb9a8106 policy.toml
|
||||
813329d5521ab5891bb86d29b362e448fae4247c949384b0687d66966546c4fe stages/legacy.json
|
||||
ca8e0059a23311d7690a0519f85dfb48883721c817cd8fe1cd481cabe14b0032 stages/mobile.json
|
||||
8bc9c9da674b4765ead0e41d54852ac105161e9638e7ae705e3982847b069e4c stages/tv.json
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
46
wai/pack-conformance/ladder-mutations/floors-swapped/LISTING
Normal file
46
wai/pack-conformance/ladder-mutations/floors-swapped/LISTING
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
00ebf8e21ba74af068445c05138d2523c801d9d27d364e1377b518540bc8b34d dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6 floor/hi/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf floor/hi/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a floor/hi/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc floor/hi/576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc.m4s
|
||||
acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74 floor/hi/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s
|
||||
58c598174eed554cd0d1bc8abedca8c9d51d992c8e3b963bcb7574da4983ed9f floor/hi/init.mp4
|
||||
2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b floor/lo/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f floor/lo/589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f.m4s
|
||||
6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db floor/lo/6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db.m4s
|
||||
d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833 floor/lo/d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833.m4s
|
||||
d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9 floor/lo/d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9.m4s
|
||||
640cd81c83baefbe13e8ccc2bf577395c6602885cc73e201ca34dc267e7f0a48 floor/lo/init.mp4
|
||||
49c1a6c22b80524c40e9509fd3c93ab4a7c1a470f4ff6df188aefe656a940a88 hls/classic/floor-hi.m3u8
|
||||
cdb43220dbeebd408a3a221829c819ca5fb7c38682f3e63ebe2039648d4ce34d hls/classic/floor-lo.m3u8
|
||||
cf34fb5f72042d09e651c8f21b68affb966bdbe55422e70c0d9d66d1a228a0af hls/classic/master.m3u8
|
||||
cf34fb5f72042d09e651c8f21b68affb966bdbe55422e70c0d9d66d1a228a0af hls/legacy/master.m3u8
|
||||
d40a6303c71682889899b2044f2d321dfd1f42b268abed0671109c61e0a67ad1 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
d40a6303c71682889899b2044f2d321dfd1f42b268abed0671109c61e0a67ad1 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
555648a512bc2da29e24dd6c2c4c2c245fb65d05a1228a8185dbb36d94299b46 index-v.wsix
|
||||
c310c5a4ae14df691a1a37a6c34e90243ed72be1e8f725d38f1babe1f6866b21 moq/catalog.json
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
2f5bd830d841eb122c63ae57a15442075478fd522ce55b6f9bcbe41acd51d8ae objects/482fbbb9b39bdb9cbf00515f94269ea591df681ced90b05d97e0df106b49b08e.wai
|
||||
0057633252a2c2ed5f71fe14ca78075c8e89fc6675a08335ac90f0e56032b825 objects/4ab8f747d181632f342a4887b0a8bb86c174409cb625157784e18c5e5559a381.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
1a6f30fdc000f258c948470386d84beef2ba5a84e922c355093837ae21fd573c objects/6ce13c9ef6872caeb7fbe71661ca88bcc463ceac47f109c8c155e34bb1a6f37d.wai
|
||||
b773dac63c9d2d9980d0d249ea956e03065c1ae40d17c512df29b690c0c820ef objects/b7bf0717e968d7b6be473785c9f66cbb814cde679bceb1ae37f0765308ddee72.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
cc8a6fc59be29bd33d96fb30ea9e5a30a5aa367a82da9c5784cbadce63396f62 objects/cf842ef3058d3c54d56c0ea3e075efdc77163602b4a1dabcff080e5ea28b9946.wai
|
||||
01b596b672de3352adb3e01b34fcddfeff975f4ee1d0b88dde160ac10c9bf929 objects/d382ba093b0595c44a43dc3aad22fc0bed42baeb3488f00dfe3567056a7009c5.wai
|
||||
9d9ae296558ce7f7d644ad0ca3af65eb4fc98d03b0bd6d7a8fc821fc952e86c1 objects/f525b4450c29153dde0b43e4d143dfb76fcc3884c607b744c8a53fa6a1728580.wai
|
||||
3d573ee482f8187b0e3bc839aeea0f51a67144588d6d207e8466fea91a0c5b34 objects/f7f7f4d6af777bfcce09cdda5ecb754ce6bd8dcd2e03ecfc41359cad989b15f7.wai
|
||||
96b7f835515e95018d614ac326d03538b055a9ac2d3a52fac97e9f2dc29dc63b package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
d1cf1bd28a06f3a9d695dd64331cb56cf793818e9e9e6cec620183d9dce37b2d policy.json
|
||||
4ba48d04e5c8e72336e32307b27b4371c96f20f7abf959ac1c1d344ccb9a8106 policy.toml
|
||||
813329d5521ab5891bb86d29b362e448fae4247c949384b0687d66966546c4fe stages/legacy.json
|
||||
ca8e0059a23311d7690a0519f85dfb48883721c817cd8fe1cd481cabe14b0032 stages/mobile.json
|
||||
8bc9c9da674b4765ead0e41d54852ac105161e9638e7ae705e3982847b069e4c stages/tv.json
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" type="static" mediaPresentationDuration="PT8.000S" minBufferTime="PT3S">
|
||||
<Period id="p0" start="PT0S">
|
||||
<AdaptationSet id="1" contentType="video" mimeType="video/mp4" segmentAlignment="true" startWithSAP="1">
|
||||
<Representation id="hi-floor" bandwidth="10440" codecs="av01.0.00M.08" width="64" height="36">
|
||||
<SegmentList timescale="25">
|
||||
<Initialization sourceURL="../floor/hi/init.mp4"/>
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="60"/>
|
||||
<S t="60" d="25"/>
|
||||
<S t="85" d="50"/>
|
||||
<S t="135" d="25"/>
|
||||
<S t="160" d="40"/>
|
||||
</SegmentTimeline>
|
||||
<SegmentURL media="../floor/hi/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s"/>
|
||||
<SegmentURL media="../floor/hi/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s"/>
|
||||
<SegmentURL media="../floor/hi/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s"/>
|
||||
<SegmentURL media="../floor/hi/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s"/>
|
||||
<SegmentURL media="../floor/hi/576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc.m4s"/>
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
<Representation id="lo-floor" bandwidth="9600" codecs="av01.0.00M.08" width="32" height="18">
|
||||
<SegmentList timescale="25">
|
||||
<Initialization sourceURL="../floor/lo/init.mp4"/>
|
||||
<SegmentTimeline>
|
||||
<S t="0" d="60"/>
|
||||
<S t="60" d="25"/>
|
||||
<S t="85" d="50"/>
|
||||
<S t="135" d="25"/>
|
||||
<S t="160" d="40"/>
|
||||
</SegmentTimeline>
|
||||
<SegmentURL media="../floor/lo/d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833.m4s"/>
|
||||
<SegmentURL media="../floor/lo/6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db.m4s"/>
|
||||
<SegmentURL media="../floor/lo/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s"/>
|
||||
<SegmentURL media="../floor/lo/589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f.m4s"/>
|
||||
<SegmentURL media="../floor/lo/d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9.m4s"/>
|
||||
</SegmentList>
|
||||
</Representation>
|
||||
</AdaptationSet>
|
||||
</Period>
|
||||
</MPD>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,18 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-TARGETDURATION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-MAP:URI="../../floor/hi/init.mp4"
|
||||
#EXTINF:2.400,
|
||||
../../floor/hi/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
#EXTINF:1.000,
|
||||
../../floor/hi/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
#EXTINF:2.000,
|
||||
../../floor/hi/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s
|
||||
#EXTINF:1.000,
|
||||
../../floor/hi/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
#EXTINF:1.600,
|
||||
../../floor/hi/576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc.m4s
|
||||
#EXT-X-ENDLIST
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-TARGETDURATION:3
|
||||
#EXT-X-MEDIA-SEQUENCE:0
|
||||
#EXT-X-PLAYLIST-TYPE:VOD
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-MAP:URI="../../floor/lo/init.mp4"
|
||||
#EXTINF:2.400,
|
||||
../../floor/lo/d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833.m4s
|
||||
#EXTINF:1.000,
|
||||
../../floor/lo/6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db.m4s
|
||||
#EXTINF:2.000,
|
||||
../../floor/lo/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
#EXTINF:1.000,
|
||||
../../floor/lo/589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f.m4s
|
||||
#EXTINF:1.600,
|
||||
../../floor/lo/d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9.m4s
|
||||
#EXT-X-ENDLIST
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=10440,AVERAGE-BANDWIDTH=8903,CODECS="av01.0.00M.08",RESOLUTION=64x36
|
||||
../classic/floor-hi.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=9600,AVERAGE-BANDWIDTH=8875,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=10440,AVERAGE-BANDWIDTH=8903,CODECS="av01.0.00M.08",RESOLUTION=64x36
|
||||
../classic/floor-hi.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=9600,AVERAGE-BANDWIDTH=8875,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=10440,AVERAGE-BANDWIDTH=8903,CODECS="av01.0.00M.08",RESOLUTION=64x36
|
||||
../classic/floor-hi.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=9600,AVERAGE-BANDWIDTH=8875,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=10440,AVERAGE-BANDWIDTH=8903,CODECS="av01.0.00M.08",RESOLUTION=64x36
|
||||
../classic/floor-hi.m3u8
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=9600,AVERAGE-BANDWIDTH=8875,CODECS="av01.0.00M.08",RESOLUTION=32x18
|
||||
../classic/floor-lo.m3u8
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
{"asset_id":"video-ladder-01","components":[{"id":"v","index":"index-v.wsix","index_digest":"fc97f47085f5d79c5ac12f3ce965a6523929b3161091256f545bf6b520cb3017","media_class":"video","stage_root":"c9825fa6105cd5a41a142e11be3d6bf85421ca1547f580315cb5542f1a2de268"}],"energy_provenance":null,"grant":{"capability":"wai.video.package","funds_ceiling":null,"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","joule_ceiling_micro":null},"joules_micro":0,"kind":"package","packager":"builtin:[email protected]","parent_kind":"none","parent_receipt_hash":null,"policy_digest":"6938df61d1274e43680652af682498bd5c7f1505c5f51b08c770143643c6388e","receipt_hash":"42c2905ad0aabfe3c1d00caa9ecc73a089e289cd01f356ed5c60ce38395663e6","sig":"2f39ed2b68c18fce1278711711bc0cde4634403ae38b2c65dd1f64944acbbdcdd723490f017376c8c017871ef64b6c7a4d6e394851fe4ebed38dd2c84309a602","signer_id":"did:key:z6MkqahniHbtvRDna9jmc9dSMo88FCuKnuujLKxbWyK4682w","signer_pubkey":"a558bae731bbba29b0b2308493514f6ec8a51ab40409cce9e3afafecca787224","stored_bytes":127966,"total_bytes":140530,"total_entries":15,"unique_objects":12,"version":2,"work_units":15}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
c5203585fea0962de941d258789157a0590e18b62a2e5d1facbd11e1cfd1ddb9 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6 floor/hi/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf floor/hi/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b floor/hi/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a floor/hi/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc floor/hi/576b7d2156c2b595bf48bf34f67996d7f89e75d105f0dc71c9841bb96e6cd0fc.m4s
|
||||
58c598174eed554cd0d1bc8abedca8c9d51d992c8e3b963bcb7574da4983ed9f floor/hi/init.mp4
|
||||
589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f floor/lo/589ad79210bff258db68916876846a2b0d530ea2e44260af66ddc51ad14ce07f.m4s
|
||||
6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db floor/lo/6bba50408f894d0e9d5516a53992fdd2aa86db43ccdc684b69c51a688856a0db.m4s
|
||||
acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74 floor/lo/acd166807d05fe4f512407f717659d88613f6d0d360485effe3751504cefbb74.m4s
|
||||
d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833 floor/lo/d0f68eb18af5b67736ddccd97ed45c4c0274b0460f6abc8f54940ac378165833.m4s
|
||||
d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9 floor/lo/d550ebb204adf0b5a7f310c7d07189e7bed63e12e34e9ed4d9bf96a101d864a9.m4s
|
||||
640cd81c83baefbe13e8ccc2bf577395c6602885cc73e201ca34dc267e7f0a48 floor/lo/init.mp4
|
||||
bfa98255d682517f58df4073bc71fdb3fcca120fc33d16e4aca873392476c586 hls/classic/floor-hi.m3u8
|
||||
ad81674ac359096ebed255a26a40e82858e53e9754702429e59d72898df33812 hls/classic/floor-lo.m3u8
|
||||
3d8ca6988d54790388e062f4ed37b3f73fd6904aefa5ed88014125e51aaf5429 hls/classic/master.m3u8
|
||||
3fd88f9dcf835edf0fcca8dff8371d2b4e897ac097fc85901477c950c0da7996 hls/legacy/master.m3u8
|
||||
09e75f55cb7ea829a4e630404d2ab540e8be0dbb1c4365dc30802359b658aed9 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
09e75f55cb7ea829a4e630404d2ab540e8be0dbb1c4365dc30802359b658aed9 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
a5140df0bf1f82b88335c13789502298a952b350ad45dfcfcd249b2b9a263a71 index-v.wsix
|
||||
c310c5a4ae14df691a1a37a6c34e90243ed72be1e8f725d38f1babe1f6866b21 moq/catalog.json
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
2f5bd830d841eb122c63ae57a15442075478fd522ce55b6f9bcbe41acd51d8ae objects/482fbbb9b39bdb9cbf00515f94269ea591df681ced90b05d97e0df106b49b08e.wai
|
||||
0057633252a2c2ed5f71fe14ca78075c8e89fc6675a08335ac90f0e56032b825 objects/4ab8f747d181632f342a4887b0a8bb86c174409cb625157784e18c5e5559a381.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
1a6f30fdc000f258c948470386d84beef2ba5a84e922c355093837ae21fd573c objects/6ce13c9ef6872caeb7fbe71661ca88bcc463ceac47f109c8c155e34bb1a6f37d.wai
|
||||
b773dac63c9d2d9980d0d249ea956e03065c1ae40d17c512df29b690c0c820ef objects/b7bf0717e968d7b6be473785c9f66cbb814cde679bceb1ae37f0765308ddee72.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
cc8a6fc59be29bd33d96fb30ea9e5a30a5aa367a82da9c5784cbadce63396f62 objects/cf842ef3058d3c54d56c0ea3e075efdc77163602b4a1dabcff080e5ea28b9946.wai
|
||||
01b596b672de3352adb3e01b34fcddfeff975f4ee1d0b88dde160ac10c9bf929 objects/d382ba093b0595c44a43dc3aad22fc0bed42baeb3488f00dfe3567056a7009c5.wai
|
||||
9d9ae296558ce7f7d644ad0ca3af65eb4fc98d03b0bd6d7a8fc821fc952e86c1 objects/f525b4450c29153dde0b43e4d143dfb76fcc3884c607b744c8a53fa6a1728580.wai
|
||||
3d573ee482f8187b0e3bc839aeea0f51a67144588d6d207e8466fea91a0c5b34 objects/f7f7f4d6af777bfcce09cdda5ecb754ce6bd8dcd2e03ecfc41359cad989b15f7.wai
|
||||
bd6086051d0452449da8a90f426f454fee368b4794f7c294218a410ded8d9088 package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
d1cf1bd28a06f3a9d695dd64331cb56cf793818e9e9e6cec620183d9dce37b2d policy.json
|
||||
4ba48d04e5c8e72336e32307b27b4371c96f20f7abf959ac1c1d344ccb9a8106 policy.toml
|
||||
813329d5521ab5891bb86d29b362e448fae4247c949384b0687d66966546c4fe stages/legacy.json
|
||||
ca8e0059a23311d7690a0519f85dfb48883721c817cd8fe1cd481cabe14b0032 stages/mobile.json
|
||||
8bc9c9da674b4765ead0e41d54852ac105161e9638e7ae705e3982847b069e4c stages/tv.json
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#EXTM3U
|
||||
#EXT-X-VERSION:7
|
||||
#EXT-X-INDEPENDENT-SEGMENTS
|
||||
#EXT-X-STREAM-INF:BANDWIDTH=10440,AVERAGE-BANDWIDTH=9055,CODECS="av01.0.00M.08",RESOLUTION=64x36
|
||||
../classic/floor-hi.m3u8
|
||||
|
|
@ -96,6 +96,24 @@
|
|||
"index": "index/floor-without-derived-from.wsix",
|
||||
"reason": "floor-derived-from"
|
||||
},
|
||||
{
|
||||
"name": "floor-only-piece",
|
||||
"kind": "index",
|
||||
"index": "index/floor-only-piece.wsix",
|
||||
"reason": "ok"
|
||||
},
|
||||
{
|
||||
"name": "floor-only-piece-with-derived-from",
|
||||
"kind": "index",
|
||||
"index": "index/floor-only-piece-with-derived-from.wsix",
|
||||
"reason": "floor-derived-from"
|
||||
},
|
||||
{
|
||||
"name": "piece-kind-none",
|
||||
"kind": "index",
|
||||
"index": "index/piece-kind-none.wsix",
|
||||
"reason": "ok"
|
||||
},
|
||||
{
|
||||
"name": "entry-flag-unknown",
|
||||
"kind": "index",
|
||||
|
|
|
|||
Binary file not shown.
BIN
wai/pack-conformance/mutations/index/floor-only-piece.wsix
Normal file
BIN
wai/pack-conformance/mutations/index/floor-only-piece.wsix
Normal file
Binary file not shown.
BIN
wai/pack-conformance/mutations/index/piece-kind-none.wsix
Normal file
BIN
wai/pack-conformance/mutations/index/piece-kind-none.wsix
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
|
@ -282,6 +282,31 @@
|
|||
"reason": "receipt-json",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "metered",
|
||||
"reason": "ok",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "metered-claim-another-figure",
|
||||
"reason": "measurement-claim-invalid",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "metered-claim-missing",
|
||||
"reason": "measurement-claim-missing",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "metered-fabrics",
|
||||
"reason": "ok",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "metered-fabrics-unknown-step",
|
||||
"reason": "measurement-claim-invalid",
|
||||
"remove": []
|
||||
},
|
||||
{
|
||||
"name": "empty-directory",
|
||||
"reason": "unexpected-file",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
9414f625ee38f54eb010a003c262ec5ed1ffc95b82510dc032da065846ff4b9b claims/01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2.measurement.json
|
||||
91b8aba2292191f2046eb841fe6fcb7b214826cecd9e06a3c193a68e7089e144 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6 floor/v/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf floor/v/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b floor/v/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a floor/v/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
99a24de04f7be9fc21097e69d2d61b222e458b1f93b8082026a9874aeb9f4c15 floor/v/99a24de04f7be9fc21097e69d2d61b222e458b1f93b8082026a9874aeb9f4c15.m4s
|
||||
58c598174eed554cd0d1bc8abedca8c9d51d992c8e3b963bcb7574da4983ed9f floor/v/init.mp4
|
||||
289bfcb09eb25124ac19597bc624371c34bea00aa1011ced9372d96b5e14b2bd hls/classic/floor-v.m3u8
|
||||
3026d7f11005e34ff66298175777ee6806d649cf9d5c1467aaa4fb00ac5987e6 hls/classic/master.m3u8
|
||||
3026d7f11005e34ff66298175777ee6806d649cf9d5c1467aaa4fb00ac5987e6 hls/legacy/master.m3u8
|
||||
c5902f61ffaf77f940c4d3dc6e850340aaaeb3f433e4541f935c32443c50b341 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
c5902f61ffaf77f940c4d3dc6e850340aaaeb3f433e4541f935c32443c50b341 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
0b65400d0bbde57ed6a2c19d90cb72ec753de0e76165e4f286acfd5a7bda14d2 index-v.wsix
|
||||
115e8e1e7ee0765de9bd2f71576cae01ce6576a5ce2a24f3374879eb74d9a6af moq/catalog.json
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
4d583d03292b77f9d46b02f2c80a774b1cda1b07df38243d18fe586b82eaca60 objects/35c7a45323b7afe979c123fa885f35cc55fabd2a1954759aebe4b95dbc76361f.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
9fdf29f5c9536d1f7701c791328d656ef725ba9b8cb3760bdb00f5a503c69338 objects/761c4649427c51796a2b699568775765b209ee80d2f4fc1cf040838c6724aa2d.wai
|
||||
a3135205d3139b39409d4e4e2aafed2c042797d293a9f9fc8664467165336e25 objects/7cc2b824ad6105abfccae27093190311b6a2348842e571684b021a213341b94a.wai
|
||||
3c66c0b25b759cd1fe6245d282ba928441b873d62e4ac62c95260e45a80fff77 objects/8f748dc64787b5c5ece081270c8559277f926322dd6ddc345306b8231f2d1752.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
4a2370c37281e64f30cb66a4bd7125854d6280855e90f00229b177bd70af3d8c package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
a497fdf29528c4744ce8548e3b430d6311f15c10ca2a4cb548fb9958d3d98f07 policy.json
|
||||
d667cfa0c1e4e52f254f3a89a80cb0d3b6be0d833c40c8b44f60be33765859b0 policy.toml
|
||||
59b343802800c815390b840da3ee6981367b495a30d8a1952f6484d04271caf9 stages/legacy.json
|
||||
b7514cb869338126e4785943a4624e9dbfa44464fa450c55341b7931bd9c19f9 stages/mobile.json
|
||||
33898452413a8fe209fb6b158bfd1e9b06a74fc3ab7639810522a586a4aa6ac6 stages/tv.json
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"body":{"bracket":null,"build":{"profile":"release","target":null,"toolchain":null},"energy_calibration_ref":null,"energy_counter_filtering":null,"energy_provenance":"ModelBased","energy_uncertainty":null,"figure_index":null,"figure_profile":"wai:video-package-receipt-id","group":null,"joules_micro":500001,"power_reader":"model:500mW"},"class":"measurement","issued_at":"2026-10-21T00:00:00Z","kind":"claim","parent_receipt_hash":null,"seq":0,"sig":"d6935698b54a44f0ff253340fe62334f30a2e8de5d4ab277496e67e666e621b87fdbd558867a7d64f5be637c9388f0cd5696d7629ad87fe89bf09194e0a35500","signer_id":"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD","signer_pubkey":"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0","subject":"01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2","subject_type":"receipt"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"asset_id":"video-base-01","components":[{"id":"v","index":"index-v.wsix","index_digest":"5ffc2be3f21af79078a1158e3d669e4268a35b0ca61a6e73ff252fdf38f346d0","media_class":"video","stage_root":"bd0c7799633b108cf9ccb0c7a4cedbdf6173a83470d3d96b94d075c03e0db7b6"}],"energy_provenance":"ModelBased","grant":{"capability":"wai.video.package","funds_ceiling":null,"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","joule_ceiling_micro":null},"joules_micro":500000,"kind":"package","packager":"builtin:[email protected]","parent_kind":"none","parent_receipt_hash":null,"policy_digest":"e1c131d56798eb840e22736076d74908bca2b7585aeae7e4bb1698218c01a35e","receipt_hash":"01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2","sig":"eb052a53c735830304a6c74230dcc348ca6d0c8110497e916407062da83e627db36ba165167ed217e451a26dfa1f5b5e21cf766725063c4532df82ff9d949604","signer_id":"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD","signer_pubkey":"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0","stored_bytes":119779,"total_bytes":130963,"total_entries":10,"unique_objects":8,"version":2,"work_units":10}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
91b8aba2292191f2046eb841fe6fcb7b214826cecd9e06a3c193a68e7089e144 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6 floor/v/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf floor/v/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b floor/v/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a floor/v/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
99a24de04f7be9fc21097e69d2d61b222e458b1f93b8082026a9874aeb9f4c15 floor/v/99a24de04f7be9fc21097e69d2d61b222e458b1f93b8082026a9874aeb9f4c15.m4s
|
||||
58c598174eed554cd0d1bc8abedca8c9d51d992c8e3b963bcb7574da4983ed9f floor/v/init.mp4
|
||||
289bfcb09eb25124ac19597bc624371c34bea00aa1011ced9372d96b5e14b2bd hls/classic/floor-v.m3u8
|
||||
3026d7f11005e34ff66298175777ee6806d649cf9d5c1467aaa4fb00ac5987e6 hls/classic/master.m3u8
|
||||
3026d7f11005e34ff66298175777ee6806d649cf9d5c1467aaa4fb00ac5987e6 hls/legacy/master.m3u8
|
||||
c5902f61ffaf77f940c4d3dc6e850340aaaeb3f433e4541f935c32443c50b341 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
c5902f61ffaf77f940c4d3dc6e850340aaaeb3f433e4541f935c32443c50b341 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
0b65400d0bbde57ed6a2c19d90cb72ec753de0e76165e4f286acfd5a7bda14d2 index-v.wsix
|
||||
115e8e1e7ee0765de9bd2f71576cae01ce6576a5ce2a24f3374879eb74d9a6af moq/catalog.json
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
4d583d03292b77f9d46b02f2c80a774b1cda1b07df38243d18fe586b82eaca60 objects/35c7a45323b7afe979c123fa885f35cc55fabd2a1954759aebe4b95dbc76361f.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
9fdf29f5c9536d1f7701c791328d656ef725ba9b8cb3760bdb00f5a503c69338 objects/761c4649427c51796a2b699568775765b209ee80d2f4fc1cf040838c6724aa2d.wai
|
||||
a3135205d3139b39409d4e4e2aafed2c042797d293a9f9fc8664467165336e25 objects/7cc2b824ad6105abfccae27093190311b6a2348842e571684b021a213341b94a.wai
|
||||
3c66c0b25b759cd1fe6245d282ba928441b873d62e4ac62c95260e45a80fff77 objects/8f748dc64787b5c5ece081270c8559277f926322dd6ddc345306b8231f2d1752.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
4a2370c37281e64f30cb66a4bd7125854d6280855e90f00229b177bd70af3d8c package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
a497fdf29528c4744ce8548e3b430d6311f15c10ca2a4cb548fb9958d3d98f07 policy.json
|
||||
d667cfa0c1e4e52f254f3a89a80cb0d3b6be0d833c40c8b44f60be33765859b0 policy.toml
|
||||
59b343802800c815390b840da3ee6981367b495a30d8a1952f6484d04271caf9 stages/legacy.json
|
||||
b7514cb869338126e4785943a4624e9dbfa44464fa450c55341b7931bd9c19f9 stages/mobile.json
|
||||
33898452413a8fe209fb6b158bfd1e9b06a74fc3ab7639810522a586a4aa6ac6 stages/tv.json
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"asset_id":"video-base-01","components":[{"id":"v","index":"index-v.wsix","index_digest":"5ffc2be3f21af79078a1158e3d669e4268a35b0ca61a6e73ff252fdf38f346d0","media_class":"video","stage_root":"bd0c7799633b108cf9ccb0c7a4cedbdf6173a83470d3d96b94d075c03e0db7b6"}],"energy_provenance":"ModelBased","grant":{"capability":"wai.video.package","funds_ceiling":null,"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","joule_ceiling_micro":null},"joules_micro":500000,"kind":"package","packager":"builtin:[email protected]","parent_kind":"none","parent_receipt_hash":null,"policy_digest":"e1c131d56798eb840e22736076d74908bca2b7585aeae7e4bb1698218c01a35e","receipt_hash":"01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2","sig":"eb052a53c735830304a6c74230dcc348ca6d0c8110497e916407062da83e627db36ba165167ed217e451a26dfa1f5b5e21cf766725063c4532df82ff9d949604","signer_id":"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD","signer_pubkey":"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0","stored_bytes":119779,"total_bytes":130963,"total_entries":10,"unique_objects":8,"version":2,"work_units":10}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
6b414a7fc1d9ee44a40aa79b9c3338d04ec00dbfbf88d39f5965ce65a6804890 claims/01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2.measurement.json
|
||||
91b8aba2292191f2046eb841fe6fcb7b214826cecd9e06a3c193a68e7089e144 dash/classic.mpd
|
||||
a3aa7658db9b85a9d9a1fed977825189f75c5adad0f5fdfcd70ebd604824c138 dash/mobile.mpd
|
||||
3f520749a6d2f231cddfd17804ee3629439f3678515e424c44e36cff54d89de9 dash/tv.mpd
|
||||
04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6 floor/v/04c948608aaa7b5d78ad5355042a2ff65b0f75d9d4b1ad78f3ebffb886a74be6.m4s
|
||||
06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf floor/v/06d5cd79ba3860cccdc252fe915de0902f941a3c6a000295714f7cab193aa6cf.m4s
|
||||
2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b floor/v/2f93f0ad449f9cc881b7816c6ab33fd445037d6538b445bcc76431f9c9f91c5b.m4s
|
||||
420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a floor/v/420ae4c941bba6e12a6c7fdb7d01dc69170ecb4240dffd2e86808ddf87baa12a.m4s
|
||||
99a24de04f7be9fc21097e69d2d61b222e458b1f93b8082026a9874aeb9f4c15 floor/v/99a24de04f7be9fc21097e69d2d61b222e458b1f93b8082026a9874aeb9f4c15.m4s
|
||||
58c598174eed554cd0d1bc8abedca8c9d51d992c8e3b963bcb7574da4983ed9f floor/v/init.mp4
|
||||
289bfcb09eb25124ac19597bc624371c34bea00aa1011ced9372d96b5e14b2bd hls/classic/floor-v.m3u8
|
||||
3026d7f11005e34ff66298175777ee6806d649cf9d5c1467aaa4fb00ac5987e6 hls/classic/master.m3u8
|
||||
3026d7f11005e34ff66298175777ee6806d649cf9d5c1467aaa4fb00ac5987e6 hls/legacy/master.m3u8
|
||||
c5902f61ffaf77f940c4d3dc6e850340aaaeb3f433e4541f935c32443c50b341 hls/mobile/master.m3u8
|
||||
c4044eca76959c9f87230484a2b207fb12c0bcaedb8d921eab806014fca53cb8 hls/mobile/wai-v.m3u8
|
||||
60a6378f46afc9308dc4ac4d138602b16568577616ea0bf4526b142d86899151 hls/mobile/wai.m3u8
|
||||
c5902f61ffaf77f940c4d3dc6e850340aaaeb3f433e4541f935c32443c50b341 hls/tv/master.m3u8
|
||||
ff4edf2929e380d205c1c74156621185feec53d593ffa968b8dfaf0d05448bcd hls/tv/wai-v.m3u8
|
||||
5d42147b308330913c3d9c8a868ca83ce4d3f3e460da8d84c84014eb24aaf9d7 hls/tv/wai.m3u8
|
||||
0b65400d0bbde57ed6a2c19d90cb72ec753de0e76165e4f286acfd5a7bda14d2 index-v.wsix
|
||||
115e8e1e7ee0765de9bd2f71576cae01ce6576a5ce2a24f3374879eb74d9a6af moq/catalog.json
|
||||
07e98fb9ef9ff3fa6bf04f9078da2a3d2423e586feea1061256131a1d1fbb943 objects/24761a434c91c627f11abd6265371966b50573602a200dd7bb04e0d90889326d.wai
|
||||
4d583d03292b77f9d46b02f2c80a774b1cda1b07df38243d18fe586b82eaca60 objects/35c7a45323b7afe979c123fa885f35cc55fabd2a1954759aebe4b95dbc76361f.wai
|
||||
cfb71256ef44758ea279846a8938823994d321abce83ef73afd2ed269a8e0259 objects/618e0a89be8c7e556b380326b219c2f6fbfc5a0cb97754e871c1c23a99bdb481.wai
|
||||
f3eedf9eb2870c0160801397412e7a4c3aca4a6e16f1a69a276e1356d38c9220 objects/61c7c3b7ccab07ed01df5b0d7ca0240598b914dbf2cabe545aa001e8de12d365.wai
|
||||
9fdf29f5c9536d1f7701c791328d656ef725ba9b8cb3760bdb00f5a503c69338 objects/761c4649427c51796a2b699568775765b209ee80d2f4fc1cf040838c6724aa2d.wai
|
||||
a3135205d3139b39409d4e4e2aafed2c042797d293a9f9fc8664467165336e25 objects/7cc2b824ad6105abfccae27093190311b6a2348842e571684b021a213341b94a.wai
|
||||
3c66c0b25b759cd1fe6245d282ba928441b873d62e4ac62c95260e45a80fff77 objects/8f748dc64787b5c5ece081270c8559277f926322dd6ddc345306b8231f2d1752.wai
|
||||
b2a40a9a8cec3dcaf6c92a07d6765203c412c57ec879b7e33a52eabb2e74609d objects/c2ce1907255a041c388afd06ccd007436368e8d20467fdec2a1eebbb84b03bf2.wai
|
||||
4a2370c37281e64f30cb66a4bd7125854d6280855e90f00229b177bd70af3d8c package.receipt.json
|
||||
517630d9bee1f5559baff3bb3e642f6aa7b5276226f6e3bdba4f3f9724794859 plan-v.txt
|
||||
a497fdf29528c4744ce8548e3b430d6311f15c10ca2a4cb548fb9958d3d98f07 policy.json
|
||||
d667cfa0c1e4e52f254f3a89a80cb0d3b6be0d833c40c8b44f60be33765859b0 policy.toml
|
||||
59b343802800c815390b840da3ee6981367b495a30d8a1952f6484d04271caf9 stages/legacy.json
|
||||
b7514cb869338126e4785943a4624e9dbfa44464fa450c55341b7931bd9c19f9 stages/mobile.json
|
||||
33898452413a8fe209fb6b158bfd1e9b06a74fc3ab7639810522a586a4aa6ac6 stages/tv.json
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"body":{"bracket":null,"build":{"profile":"release","target":null,"toolchain":null},"energy_calibration_ref":null,"energy_counter_filtering":null,"energy_provenance":"ModelBased","energy_uncertainty":null,"fabrics":{"shares":[{"attribution":"task","fabric":"cpu","joules_micro":500000}],"steps":[{"backend":"cpu: the reference implementation","fabric":"cpu","step":"base-code"},{"backend":"cpu: the reference implementation","fabric":"cpu","step":"lunch"}]},"figure_index":null,"figure_profile":"wai:video-package-receipt-id","group":null,"joules_micro":500000,"power_reader":"model:500mW"},"class":"measurement","issued_at":"2026-10-21T00:00:00Z","kind":"claim","parent_receipt_hash":null,"seq":0,"sig":"3d1c9daf255ffd4eded9a09f2ed4d9376ff7a94a28e179e2961aa354255aa3bcca287ac7d201a610fa5f87a527fb31718729fddd5982cb3fe2398a4b1e884806","signer_id":"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD","signer_pubkey":"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0","subject":"01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2","subject_type":"receipt"}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{"asset_id":"video-base-01","components":[{"id":"v","index":"index-v.wsix","index_digest":"5ffc2be3f21af79078a1158e3d669e4268a35b0ca61a6e73ff252fdf38f346d0","media_class":"video","stage_root":"bd0c7799633b108cf9ccb0c7a4cedbdf6173a83470d3d96b94d075c03e0db7b6"}],"energy_provenance":"ModelBased","grant":{"capability":"wai.video.package","funds_ceiling":null,"grant_hash":"0000000000000000000000000000000000000000000000000000000000000000","joule_ceiling_micro":null},"joules_micro":500000,"kind":"package","packager":"builtin:[email protected]","parent_kind":"none","parent_receipt_hash":null,"policy_digest":"e1c131d56798eb840e22736076d74908bca2b7585aeae7e4bb1698218c01a35e","receipt_hash":"01d891a6690096d3de7c8e7cec802cb95f82deb84b5d45994dd713e7576673f2","sig":"eb052a53c735830304a6c74230dcc348ca6d0c8110497e916407062da83e627db36ba165167ed217e451a26dfa1f5b5e21cf766725063c4532df82ff9d949604","signer_id":"did:key:z6MkfMo6gxqdBhaHMNnmfhgZFBjpCDTkmJMJLoypsBZS9PwD","signer_pubkey":"0d7550754e0800a5d237eef5826035766b9b3e5a15868a940ab289958788e3b0","stored_bytes":119779,"total_bytes":130963,"total_entries":10,"unique_objects":8,"version":2,"work_units":10}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue