joule-moq-ingest: WHIP into a room's MoQ tracks and WHEP out of them, receipts signed at the first hop; live-ingest maps E-RTMP v2 and SRT (L6a) #325

Merged
dcharlot merged 57 commits from joule-moq/ingest into main 2026-10-19 05:55:10 -04:00
Owner

L6a: WHIP ingest and WHEP egress for rooms

A new crate, joule-moq-ingest (library and binary), lets a standard WebRTC encoder publish into a room's MoQ tracks over WHIP (RFC 9725):

  • the gateway becomes the tracks' MoQ publisher at the relay;
  • it signs every group's receipts as the first hop.

A standard WebRTC player watches a room track over WHEP (draft-ietf-wish-whep-05). Every object's receipt is verified before the player gets the frame.

The mapping is a new WAI extension, wai/extensions/live-ingest.md. It specifies E-RTMP v2 multitrack (v2-2026-01-31-r2) and SRT 1.5.7 as mappings only. Two payload capabilities are registered so WebRTC media can be packaged at all: wai.video.vp8 and wai.audio.opus.packet.

What is implemented and what is only specified

status
WHIP: POST/201 + Location + ETag, PATCH trickle ICE and ICE restart (If-Match, 428/412), DELETE, OPTIONS/CORS implemented
Bearer token = room grant (WRG1, base64url), checked against the room's signed descriptor; publish power per media kind, else 403 insufficient_scope (RFC 6750 §3.1) implemented
RTP depacketised to frames (str0m): H.264 (RFC 6184), VP8 (RFC 7741), VP9 (RFC 9628), AV1 (RTP payload format v1.0.0), Opus (RFC 7587) implemented, all five tested end to end
Packaging per moq-streaming-format: one frame = one WAI1 envelope; one group per keyframe for video, per fixed 200 ms window for audio implemented
Publishing the tracks and a signed MSF catalog to the relay; first-hop receipts per group on the receipts tracks implemented
Timeline: RTP timestamp to room clock, with a stated bound implemented
WHEP: subscribe at the relay, verify every object's receipt, drop and count failures, packetise back to RTP implemented
Bounds: sessions, bitrate, tracks per session, frame size, relay queue (config fields, README, example config) implemented
E-RTMP v2 multitrack: multitrack to room tracks, FourCCs to catalog, reconnect request, latency specified (live-ingest §12)
SRT 1.5.7: stream ID to room and grant, rejection codes, MPEG-TS stream types, latency specified (live-ingest §13)

Design decisions worth reviewing

  1. The spec is its own extension, live-ingest.md, not a section of moq-streaming-format.

    • moq-streaming-format says what a WAI track is on MoQ.
    • This text binds four other protocols (WHIP, WHEP, RTMP, SRT) onto those tracks under the wai.live family's rooms and grants.
    • Inside moq-streaming-format it would make a MoQ format describe RTMP and SRT.
    • It sits beside live-rooms, live-events and capture-binding, and changes none of them.
  2. The token is the grant itself. The WHIP/WHEP bearer token is the WRG1 record, base64url without padding. The gateway checks it the way live-rooms §4.3 checks a grant, then checks:

    • its window;
    • the room's revocations;
    • its origins;
    • its single-use nonce.

    A refused grant gets 401 invalid_token; a good grant without the needed power gets 403 insufficient_scope.

    The gateway's room reader is an independent implementation written from the text. tests/room_corpus.rs holds it to wai/live-conformance/rooms/:

    • every room id and grant digest;
    • the registered profiles' records;
    • every refusal of a descriptor or grant, except an exchange's link and the key log, which admission doesn't make.
  3. Two capabilities are registered (SPEC §5, broadcast/streaming table, sink-supplied, neither a default):

    • wai.video.vp8: one-frame IVF, FourCC VP80, like VP9. StandardUnclassified, because RFC 6386's conformance text was not read.
    • wai.audio.opus.packet: one Opus packet with no container. Sending an Ogg Opus stream per 20 ms packet would be wrong twice over: it adds three Ogg pages to every packet, and its "whole stream" semantics would reset decoder state each object.
    • live-ingest §5.1 states the live rule: a track's objects, from a group's first, are consecutive units of one stream.
  4. The catalog is signed too, on catalog/receipts. A relay with a door on the room's namespace (jwp-receipts §4.1) refuses objects of any track whose receipts never come, and that includes a catalog. This turned up in the e2e tests. WHEP reads a catalog only after its receipt verifies.

  5. Receipts are published after a group's last object. The group receipt's root covers every object, and frames stream as they arrive.

    • This differs from moq-streaming-format §4.1's staged-set rule (one object per group, receipt first). §8.3 says so and explains why.
    • Consequence: a verifying sink, the WHEP gateway included, holds a group until it ends. WHEP latency is therefore up to one video group, bounded by the keyframe requests at max_video_group_ms. §9.4 states this.
  6. Group IDs. A track's first group ID is its first frame's room-clock time in µs, and IDs then ascend by one. A reconnecting publisher continues above the old IDs, so the relay never dedups a new group as a copy (MOQT §7.3).

    • The receipt chain alias is derived per session, so a reconnect starts a new chain and never forks the old one.
  7. The timeline and its bound.

    • Each track is anchored at its first frame's arrival: t = A + (R − R0)·10^6/rate.
    • It re-anchors at any frame it would place more than max_skew_ms (250) from its arrival. t never decreases.
    • So every frame's t lies within max_skew_ms of its arrival at the gateway.
    • Two frames of one session are placed within their arrival difference ± 2·max_skew_ms.
    • The bound is to the gateway's clock; the gateway's own clock error is stated as unmeasured.
    • t rides in the manifest's new live member, with the RTP time, the rate and the keyframe flag. On a group's first object it also carries the live-rooms §4.5 grant reference, so every object names its grant through the signed group receipt.
  8. What a WHEP player cannot see: the receipts. The gateway verifies on the player's behalf:

    • the receipt's group, Object ID and chain alias;
    • the signature under a trusted first hop;
    • the parent link;
    • the inclusion proof;
    • that the object opens as an envelope.

    Failures are dropped and counted in the session's signed record. A WAI-aware player subscribes to the same tracks and receipts tracks over MoQ (the relay's WebTransport or WebSocket paths) and verifies each object itself (§9.3).

  9. Ingest and capture binding (§15). An ingest is the live tier and is never a single-take. A device that speaks L4 still commits its take with take_commit events on its own events track over MoQ, beside its WHIP session. The two share the room clock, so a marker set on the live call lands in the master within both bounds.

The WebRTC stack: str0m 0.24.1, pinned

  • Licence and backend. MIT OR Apache-2.0. Built with default-features = false, features = ["rust-crypto"]: DTLS by its own dimpl, SRTP on RustCrypto, no C library linked.
  • Sans-IO. Each session owns its UDP socket and drives Rtc from a tokio task.
  • Codecs. Frame-level depacketise and packetise for H.264, VP8, VP9, AV1 and Opus. AV1 is a hidden codec in its public API, but this PR's codecs test takes AV1 through it end to end.
  • ICE and RTCP. ICE lite and full ICE, ICE restarts, NACK and PLI.
  • Maintenance. Active, with regular releases through 2026. 0.24.1 was released 2026-10-03.

Every crate new to the lockfile is MIT, Apache-2.0 or ISC: str0m, str0m-proto, str0m-rust-crypto, dimpl, is, sctp-proto, hyper 1.11.1 (pinned), hyper-util 0.1.20 (pinned), http-body-util 0.1.5 (pinned), http-body, httpdate, atomic-waker, ccm, crc, crc-catalog, der_derive, fastrand, flagset, nom, untrusted, x509-cert. The lockfile check passes.

The seam for WMH1 (#317)

The PR does not depend on #317. Each WHIP and WHEP session ends with a SessionRecord (record.rs, format joule-moq-ingest/session/1), signed by the gateway's key. It is kept in exactly the shape of #317's per-hop record:

here WMH1
TrackEntry track entries: kind, track digest (live-events §3.1), direction, objects, bytes, drops by reason, receipted
sessions, subscriber_us[9] subscriber sessions and time by layer class (WHEP = 1 session at its layer)
LedgerEntry ledger entry: grant digest, ceilings, currency, sessions, bytes in and out, subscriber time
energy always the unmetered marker
role 3, ingest gateway

Records go to a record::HopSink trait. The default sink keeps them (Gateway::records) and appends JSON lines.

Once #317 lands, a Wmh1Sink: HopSink aggregates a room session's records into one WMH1 and signs it with the same key. The key is listed under live/ingest in the room's key log. Nothing else in the gateway changes. live-ingest §16 states this without citing the unmerged text.

rb results (build host, under memguard)

# command (in /Users/dcharlot/data-share/vibe-coding/_wt-moq-ingest) exit
1 rb --pull <wt> -- 'cd joule-code/joule-code-rs && cargo check -p joule-moq-ingest' (dependency resolution, lockfile) 0
2 rb <wt> -- 'cd joule-code/joule-code-rs && cargo test -p joule-moq-ingest --tests --no-fail-fast' 0 (lib 23, room_corpus 2, auth_and_ice 2, codecs 3, whep_play 1, whip_publish 1)
3 rb <wt> -- 'cd joule-code/joule-code-rs && cargo clippy -p joule-moq-ingest --all-targets' 0, no warning in the crate
4 Loop under concurrent load: rb <wt> -- '… cargo test -p joule-moq-ingest --tests --no-run …; copy whip_publish whep_play auth_and_ice codecs; for i in 1..32: run all four binaries × 4 concurrent copies (16 processes at once), wait, count' 0: 32 iterations, 512 process runs, 0 failures (each run holds 1–3 tests, 896 test executions in all)
5 rb <wt> -- './scripts/check-lockfiles.sh joule-code/joule-code-rs wai/wai-rs; cd wai/wai-rs && cargo run --no-default-features --bin wai -- registry export --check; cargo test --lib --no-default-features codecs' 0 (2 lockfiles current; export up to date; 54 passed)
6 rb <wt> -- 'cd joule-code/joule-code-rs && cargo test --locked -p joule-moq-ingest --all-targets; cargo check --locked --workspace --all-targets' (the new CI job's command, and the workspace with the new member) 0

The registry commits were made in a separate worktree (_wt-moq-ingest-reg) and cherry-picked here. They passed:

  • wai registry export with --pull, then --check;
  • cargo test --lib --no-default-features codecs: 54 passed;
  • the carriage, manifest-builder, CLI and corpus-provenance tests;
  • CI's lib step with its features: 838 passed.

All exited 0.

Local scripts, all exit 0:

  • check-tests-in-ci.py (no entry needed: the job runs every target);
  • check-wai-section-refs.py;
  • check-spec-refs.py;
  • check-no-vanity-metrics.sh;
  • check-comparative-prose.sh;
  • check-workflow-concurrency.sh.

check-public-names.sh needs the CI secret and was not run locally. The added wai/ text was reviewed by hand and names only protocols, standards and open-source projects.

The forge's CI was not waited for.

Tests (all loopback, every wait bounded, the relay checking every receipt at its door)

  • whip_publish: a WHIP client (str0m) sends 3 s of synthetic H.264 and Opus. A relay subscriber:

    • receives every group;
    • verifies every receipt and chain link under the gateway key;
    • gets frames whose bytes equal what was sent.

    It also checks:

    • one group per keyframe;
    • every audio group within its window;
    • DELETE gives 200, and the relay sends PUBLISH_DONE on both tracks;
    • a second DELETE gives 404;
    • the session record verifies.
  • whep_play:

    • before the publisher is live, WHEP gets 409 with Retry-After;
    • after that, a WHEP client (str0m) receives a relay track and gets the same frames, byte for byte, from the keyframe of the group it joined through the last closed group (video and audio);
    • the WHEP session ends with the tracks, with nothing refused.
  • auth_and_ice:

    • a token without publish power gets 403 insufficient_scope;
    • no token, a non-grant token, an expired grant and a grant for an unknown room each get 401;
    • a wrong content type gets 415; a data-channel-only offer gets 422;
    • WHEP without subscribe, or above max_layer, gets 403;
    • HEAD discovery and CORS work;
    • PATCH without If-Match gets 428, a stale tag 412, trickle 204 with no ETag, another principal's token 403;
    • an ICE restart (If-Match: *) returns 200 with new credentials and a new ETag, ICE reconnects, the old ETag becomes stale, and every frame before and after the restart reaches the relay.
  • codecs: VP8, VP9 and AV1 each go through WHIP whole, one group per keyframe, with each framing stating the right picture size.

  • room_corpus: the room reader against the live-rooms corpus.

CI

The new job moq-ingest in joule-code-conformance.yml runs cargo test --locked -p joule-moq-ingest --all-targets on linux-arm64. This follows how #293 added moq-relay:

  • the workspace step now excludes the gateway too;
  • the trigger paths add wai/live-conformance/rooms/**, which the corpus test reads.

Not implemented (also listed in the README and live-ingest §17)

  • E-RTMP v2 and SRT gateways (mapped only);
  • the WHEP counter-offer (406);
  • forwarding a player's PLI upstream (MOQT NEW_GROUP_REQUEST);
  • publishing into a slot;
  • simulcast within one WHIP session;
  • STUN/TURN Link headers;
  • reading the key log itself (revocations come from a configured list).

Merge notes

  • #317 (wai/live-metering) touches wai/CHANGELOG.md, SPEC.md, REGISTRY.md, registry/capabilities.json, vocab/* and codecs/registry.rs. Whichever lands second needs a merge, then a wai registry export rerun.
  • #324 (joule-moq/web) touches the joule-code workspace Cargo.toml and Cargo.lock, the conformance workflow and the joule-code CHANGELOG.
  • Nothing deployed: every listener binds loopback, and there is no Caddy, DNS, tunnel or firewall change.

Proof of possession (round 2, head 7181b8a0): stacked on #327

This PR is now stacked on #327 (L5 hardening, which carries #324). It merges joule-moq/harden at its current head, which carries main with live-metering (#317). Merging #327 first leaves this PR's own diff.

A room grant is public, so presenting it as the Bearer token proved nothing about who presents it. Following live-rooms §10.5.3 (from #327), the gateway now does the following:

  1. Checks a proof on every request that carries a grant. That covers POST /whip and POST /whep, plus PATCH, DELETE, GET and HEAD on a session's URL. The proof is WAI-Room-Proof, a WRP1 record in base64url, over a challenge the gateway holds for the request's scope (the endpoint, or the session) and the context METHOD //authority/path. The check is gateway::check_http_proof, the proof step of authorize() written as a function, on joule_moq::grant_proof::check_proof, with the gateway's own reading of the grant.
  2. Issues single-use challenges in WAI-Room-Challenge, on a 401 and on every response to a request with a grant; a 201's is scoped to the new session. A proof that passes consumes its challenge, and the consumed ones are kept, so a proof presented again is pop_replayed. New bounds: http.challenge_ttl_s (300) and http.max_challenges (65536).
  3. CORS: allows WAI-Room-Proof and exposes WAI-Room-Challenge.
  4. Names: the receipts tracks are 0_receipts and catalog_receipts (live-rooms §6.1).
  5. Corpus: room_corpus runs the six HTTP proofs of wai/live-conformance/rooms/vectors.json through check_http_proof, each with its stated outcome (two accepted; pop_missing, pop_replayed, and pop_wrong_context twice). It presents each accepted proof a second time and requires pop_replayed. The gateway's grant reader and joule-moq's agree on every grant's digest and principal.

auth_and_ice presents grants with their proofs through the test Auth helper. It shows a session's PATCH and DELETE refusing a grant presented without a proof (401 pop_missing), and the session staying up.

Docs: live-ingest §3.1, §3.2 step 7 (the proof), §3.4, §4.1 (_receipts), §10 (the challenge bounds) and §14 (the eight pop_* reasons); the crate README; both CHANGELOGs.

Runs on the build host (rb, under memguard):

run result
cargo test --locked -p joule-moq-ingest --all-targets 0, every binary passed (unit 23, room_corpus 3, auth_and_ice 2, codecs, whip_publish, whep_play)
cargo clippy --locked -p joule-moq-ingest --all-targets 0, no warnings in the crate
cargo check --locked --workspace --all-targets 0
wai registry export --check; --lib codecs::registry 0
e2e under load: 4 concurrent loops × 8 runs, each running whip_publish, whep_play, codecs, auth_and_ice and room_corpus, load average 57–89 32 of 32 passed
repo text checks: spec refs, section refs, vanity metrics, comparative prose, tests-in-CI, orphan sources 0

🤖 Generated with Claude Code

## L6a: WHIP ingest and WHEP egress for rooms A new crate, `joule-moq-ingest` (library and binary), lets a standard WebRTC encoder publish into a room's MoQ tracks over **WHIP (RFC 9725)**: - the gateway becomes the tracks' MoQ publisher at the relay; - it signs every group's receipts as the first hop. A standard WebRTC player watches a room track over **WHEP (`draft-ietf-wish-whep-05`)**. Every object's receipt is verified before the player gets the frame. The mapping is a new WAI extension, `wai/extensions/live-ingest.md`. It specifies **E-RTMP v2 multitrack** (`v2-2026-01-31-r2`) and **SRT 1.5.7** as mappings only. Two payload capabilities are registered so WebRTC media can be packaged at all: `wai.video.vp8` and `wai.audio.opus.packet`. ### What is implemented and what is only specified | | status | |---|---| | WHIP: POST/201 + `Location` + `ETag`, PATCH trickle ICE and ICE restart (`If-Match`, `428`/`412`), DELETE, OPTIONS/CORS | **implemented** | | Bearer token = room grant (`WRG1`, base64url), checked against the room's signed descriptor; publish power per media kind, else `403 insufficient_scope` (RFC 6750 §3.1) | **implemented** | | RTP depacketised to frames (str0m): H.264 (RFC 6184), VP8 (RFC 7741), VP9 (RFC 9628), AV1 (RTP payload format v1.0.0), Opus (RFC 7587) | **implemented**, all five tested end to end | | Packaging per moq-streaming-format: one frame = one `WAI1` envelope; one group per keyframe for video, per fixed 200 ms window for audio | **implemented** | | Publishing the tracks and a signed MSF catalog to the relay; first-hop receipts per group on the receipts tracks | **implemented** | | Timeline: RTP timestamp to room clock, with a stated bound | **implemented** | | WHEP: subscribe at the relay, verify every object's receipt, drop and count failures, packetise back to RTP | **implemented** | | Bounds: sessions, bitrate, tracks per session, frame size, relay queue (config fields, README, example config) | **implemented** | | E-RTMP v2 multitrack: multitrack to room tracks, FourCCs to catalog, reconnect request, latency | **specified** (live-ingest §12) | | SRT 1.5.7: stream ID to room and grant, rejection codes, MPEG-TS stream types, latency | **specified** (live-ingest §13) | ### Design decisions worth reviewing 1. **The spec is its own extension, `live-ingest.md`, not a section of moq-streaming-format.** - moq-streaming-format says what a WAI track *is* on MoQ. - This text binds four other protocols (WHIP, WHEP, RTMP, SRT) onto those tracks under the `wai.live` family's rooms and grants. - Inside moq-streaming-format it would make a MoQ format describe RTMP and SRT. - It sits beside live-rooms, live-events and capture-binding, and changes none of them. 2. **The token is the grant itself.** The WHIP/WHEP bearer token is the `WRG1` record, base64url without padding. The gateway checks it the way live-rooms §4.3 checks a grant, then checks: - its window; - the room's revocations; - its origins; - its single-use nonce. A refused grant gets `401 invalid_token`; a good grant without the needed power gets `403 insufficient_scope`. The gateway's room reader is an independent implementation written from the text. `tests/room_corpus.rs` holds it to `wai/live-conformance/rooms/`: - every room id and grant digest; - the registered profiles' records; - every refusal of a descriptor or grant, except an exchange's link and the key log, which admission doesn't make. 3. **Two capabilities are registered (SPEC §5, broadcast/streaming table, sink-supplied, neither a default):** - **`wai.video.vp8`**: one-frame IVF, FourCC `VP80`, like VP9. `StandardUnclassified`, because RFC 6386's conformance text was not read. - **`wai.audio.opus.packet`**: one Opus packet with no container. Sending an Ogg Opus stream per 20 ms packet would be wrong twice over: it adds three Ogg pages to every packet, and its "whole stream" semantics would reset decoder state each object. - live-ingest §5.1 states the live rule: a track's objects, from a group's first, are consecutive units of one stream. 4. **The catalog is signed too, on `catalog/receipts`.** A relay with a door on the room's namespace (jwp-receipts §4.1) refuses objects of any track whose receipts never come, and that includes a catalog. This turned up in the e2e tests. WHEP reads a catalog only after its receipt verifies. 5. **Receipts are published after a group's last object.** The group receipt's root covers every object, and frames stream as they arrive. - This differs from moq-streaming-format §4.1's staged-set rule (one object per group, receipt first). §8.3 says so and explains why. - Consequence: a verifying sink, the WHEP gateway included, holds a group until it ends. WHEP latency is therefore up to one video group, bounded by the keyframe requests at `max_video_group_ms`. §9.4 states this. 6. **Group IDs.** A track's first group ID is its first frame's room-clock time in µs, and IDs then ascend by one. A reconnecting publisher continues above the old IDs, so the relay never dedups a new group as a copy (MOQT §7.3). - The receipt chain alias is derived per session, so a reconnect starts a new chain and never forks the old one. 7. **The timeline and its bound.** - Each track is anchored at its first frame's arrival: `t = A + (R − R0)·10^6/rate`. - It re-anchors at any frame it would place more than `max_skew_ms` (250) from its arrival. `t` never decreases. - So every frame's `t` lies within `max_skew_ms` of its arrival at the gateway. - Two frames of one session are placed within their arrival difference ± 2·`max_skew_ms`. - The bound is to the gateway's clock; the gateway's own clock error is stated as unmeasured. - `t` rides in the manifest's new `live` member, with the RTP time, the rate and the keyframe flag. On a group's first object it also carries the live-rooms §4.5 grant reference, so every object names its grant through the signed group receipt. 8. **What a WHEP player cannot see: the receipts.** The gateway verifies on the player's behalf: - the receipt's group, Object ID and chain alias; - the signature under a trusted first hop; - the parent link; - the inclusion proof; - that the object opens as an envelope. Failures are dropped and counted in the session's signed record. A WAI-aware player subscribes to the same tracks and receipts tracks over MoQ (the relay's WebTransport or WebSocket paths) and verifies each object itself (§9.3). 9. **Ingest and capture binding (§15).** An ingest is the live tier and is never a `single-take`. A device that speaks L4 still commits its take with `take_commit` events on its own events track over MoQ, beside its WHIP session. The two share the room clock, so a marker set on the live call lands in the master within both bounds. ### The WebRTC stack: str0m 0.24.1, pinned - **Licence and backend.** MIT OR Apache-2.0. Built with `default-features = false, features = ["rust-crypto"]`: DTLS by its own `dimpl`, SRTP on RustCrypto, no C library linked. - **Sans-IO.** Each session owns its UDP socket and drives `Rtc` from a tokio task. - **Codecs.** Frame-level depacketise and packetise for H.264, VP8, VP9, AV1 and Opus. AV1 is a hidden codec in its public API, but this PR's `codecs` test takes AV1 through it end to end. - **ICE and RTCP.** ICE lite and full ICE, ICE restarts, NACK and PLI. - **Maintenance.** Active, with regular releases through 2026. 0.24.1 was released 2026-10-03. Every crate new to the lockfile is MIT, Apache-2.0 or ISC: str0m, str0m-proto, str0m-rust-crypto, dimpl, is, sctp-proto, hyper 1.11.1 (pinned), hyper-util 0.1.20 (pinned), http-body-util 0.1.5 (pinned), http-body, httpdate, atomic-waker, ccm, crc, crc-catalog, der_derive, fastrand, flagset, nom, untrusted, x509-cert. The lockfile check passes. ### The seam for `WMH1` (#317) The PR does not depend on #317. Each WHIP and WHEP session ends with a `SessionRecord` (`record.rs`, format `joule-moq-ingest/session/1`), signed by the gateway's key. It is kept in exactly the shape of #317's per-hop record: | here | `WMH1` | |---|---| | `TrackEntry` | track entries: kind, track digest (live-events §3.1), direction, objects, bytes, drops by reason, receipted | | `sessions`, `subscriber_us[9]` | subscriber sessions and time by layer class (WHEP = 1 session at its layer) | | `LedgerEntry` | ledger entry: grant digest, ceilings, currency, sessions, bytes in and out, subscriber time | | `energy` | always the unmetered marker | | `role` | 3, ingest gateway | Records go to a `record::HopSink` trait. The default sink keeps them (`Gateway::records`) and appends JSON lines. Once #317 lands, a `Wmh1Sink: HopSink` aggregates a room session's records into one `WMH1` and signs it with the same key. The key is listed under `live/ingest` in the room's key log. Nothing else in the gateway changes. live-ingest §16 states this without citing the unmerged text. ### rb results (build host, under memguard) | # | command (in `/Users/dcharlot/data-share/vibe-coding/_wt-moq-ingest`) | exit | |---|---|---| | 1 | `rb --pull <wt> -- 'cd joule-code/joule-code-rs && cargo check -p joule-moq-ingest'` (dependency resolution, lockfile) | 0 | | 2 | `rb <wt> -- 'cd joule-code/joule-code-rs && cargo test -p joule-moq-ingest --tests --no-fail-fast'` | 0 (lib 23, room_corpus 2, auth_and_ice 2, codecs 3, whep_play 1, whip_publish 1) | | 3 | `rb <wt> -- 'cd joule-code/joule-code-rs && cargo clippy -p joule-moq-ingest --all-targets'` | 0, no warning in the crate | | 4 | **Loop under concurrent load:** `rb <wt> -- '… cargo test -p joule-moq-ingest --tests --no-run …; copy whip_publish whep_play auth_and_ice codecs; for i in 1..32: run all four binaries × 4 concurrent copies (16 processes at once), wait, count'` | 0: **32 iterations, 512 process runs, 0 failures** (each run holds 1–3 tests, 896 test executions in all) | | 5 | `rb <wt> -- './scripts/check-lockfiles.sh joule-code/joule-code-rs wai/wai-rs; cd wai/wai-rs && cargo run --no-default-features --bin wai -- registry export --check; cargo test --lib --no-default-features codecs'` | 0 (2 lockfiles current; export up to date; 54 passed) | | 6 | `rb <wt> -- 'cd joule-code/joule-code-rs && cargo test --locked -p joule-moq-ingest --all-targets; cargo check --locked --workspace --all-targets'` (the new CI job's command, and the workspace with the new member) | 0 | The registry commits were made in a separate worktree (`_wt-moq-ingest-reg`) and cherry-picked here. They passed: - `wai registry export` with `--pull`, then `--check`; - `cargo test --lib --no-default-features codecs`: 54 passed; - the carriage, manifest-builder, CLI and corpus-provenance tests; - CI's lib step with its features: 838 passed. All exited 0. Local scripts, all exit 0: - `check-tests-in-ci.py` (no entry needed: the job runs every target); - `check-wai-section-refs.py`; - `check-spec-refs.py`; - `check-no-vanity-metrics.sh`; - `check-comparative-prose.sh`; - `check-workflow-concurrency.sh`. `check-public-names.sh` needs the CI secret and was not run locally. The added `wai/` text was reviewed by hand and names only protocols, standards and open-source projects. The forge's CI was not waited for. ### Tests (all loopback, every wait bounded, the relay checking every receipt at its door) - **`whip_publish`**: a WHIP client (str0m) sends 3 s of synthetic H.264 and Opus. A relay subscriber: - receives every group; - verifies every receipt and chain link under the gateway key; - gets frames whose bytes equal what was sent. It also checks: - one group per keyframe; - every audio group within its window; - DELETE gives `200`, and the relay sends PUBLISH_DONE on both tracks; - a second DELETE gives `404`; - the session record verifies. - **`whep_play`**: - before the publisher is live, WHEP gets `409` with `Retry-After`; - after that, a WHEP client (str0m) receives a relay track and gets the same frames, byte for byte, from the keyframe of the group it joined through the last closed group (video and audio); - the WHEP session ends with the tracks, with nothing refused. - **`auth_and_ice`**: - a token without publish power gets `403 insufficient_scope`; - no token, a non-grant token, an expired grant and a grant for an unknown room each get `401`; - a wrong content type gets `415`; a data-channel-only offer gets `422`; - WHEP without subscribe, or above `max_layer`, gets `403`; - HEAD discovery and CORS work; - PATCH without `If-Match` gets `428`, a stale tag `412`, trickle `204` with no ETag, another principal's token `403`; - **an ICE restart** (`If-Match: *`) returns `200` with new credentials and a new ETag, ICE reconnects, the old ETag becomes stale, and every frame before and after the restart reaches the relay. - **`codecs`**: VP8, VP9 and AV1 each go through WHIP whole, one group per keyframe, with each framing stating the right picture size. - **`room_corpus`**: the room reader against the live-rooms corpus. ### CI The new job `moq-ingest` in `joule-code-conformance.yml` runs `cargo test --locked -p joule-moq-ingest --all-targets` on **linux-arm64**. This follows how #293 added `moq-relay`: - the workspace step now excludes the gateway too; - the trigger paths add `wai/live-conformance/rooms/**`, which the corpus test reads. ### Not implemented (also listed in the README and live-ingest §17) - E-RTMP v2 and SRT gateways (mapped only); - the WHEP counter-offer (406); - forwarding a player's PLI upstream (MOQT `NEW_GROUP_REQUEST`); - publishing into a slot; - simulcast within one WHIP session; - STUN/TURN `Link` headers; - reading the key log itself (revocations come from a configured list). ### Merge notes - **#317 (`wai/live-metering`)** touches `wai/CHANGELOG.md`, `SPEC.md`, `REGISTRY.md`, `registry/capabilities.json`, `vocab/*` and `codecs/registry.rs`. Whichever lands second needs a merge, then a `wai registry export` rerun. - **#324 (`joule-moq/web`)** touches the joule-code workspace `Cargo.toml` and `Cargo.lock`, the conformance workflow and the joule-code CHANGELOG. - Nothing deployed: every listener binds loopback, and there is no Caddy, DNS, tunnel or firewall change. --- ## Proof of possession (round 2, head `7181b8a0`): stacked on #327 **This PR is now stacked on #327** (L5 hardening, which carries #324). It merges `joule-moq/harden` at its current head, which carries main with live-metering (#317). Merging #327 first leaves this PR's own diff. A room grant is public, so presenting it as the Bearer token proved nothing about who presents it. Following live-rooms §10.5.3 (from #327), the gateway now does the following: 1. **Checks a proof on every request that carries a grant.** That covers `POST /whip` and `POST /whep`, plus `PATCH`, `DELETE`, `GET` and `HEAD` on a session's URL. The proof is `WAI-Room-Proof`, a `WRP1` record in base64url, over a challenge the gateway holds for the request's scope (the endpoint, or the session) and the context `METHOD //authority/path`. The check is `gateway::check_http_proof`, the proof step of `authorize()` written as a function, on `joule_moq::grant_proof::check_proof`, with the gateway's own reading of the grant. 2. **Issues single-use challenges** in `WAI-Room-Challenge`, on a `401` and on every response to a request with a grant; a `201`'s is scoped to the new session. A proof that passes consumes its challenge, and the consumed ones are kept, so a proof presented again is `pop_replayed`. New bounds: `http.challenge_ttl_s` (300) and `http.max_challenges` (65536). 3. **CORS:** allows `WAI-Room-Proof` and exposes `WAI-Room-Challenge`. 4. **Names:** the receipts tracks are `0_receipts` and `catalog_receipts` (live-rooms §6.1). 5. **Corpus:** `room_corpus` runs the six HTTP proofs of `wai/live-conformance/rooms/vectors.json` through `check_http_proof`, each with its stated outcome (two accepted; `pop_missing`, `pop_replayed`, and `pop_wrong_context` twice). It presents each accepted proof a second time and requires `pop_replayed`. The gateway's grant reader and `joule-moq`'s agree on every grant's digest and principal. `auth_and_ice` presents grants with their proofs through the test `Auth` helper. It shows a session's `PATCH` and `DELETE` refusing a grant presented without a proof (`401 pop_missing`), and the session staying up. **Docs:** live-ingest §3.1, §3.2 step 7 (the proof), §3.4, §4.1 (`_receipts`), §10 (the challenge bounds) and §14 (the eight `pop_*` reasons); the crate README; both CHANGELOGs. **Runs on the build host (rb, under memguard):** | run | result | |---|---| | `cargo test --locked -p joule-moq-ingest --all-targets` | 0, every binary passed (unit 23, `room_corpus` 3, `auth_and_ice` 2, `codecs`, `whip_publish`, `whep_play`) | | `cargo clippy --locked -p joule-moq-ingest --all-targets` | 0, no warnings in the crate | | `cargo check --locked --workspace --all-targets` | 0 | | `wai registry export --check`; `--lib codecs::registry` | 0 | | e2e under load: 4 concurrent loops × 8 runs, each running `whip_publish`, `whep_play`, `codecs`, `auth_and_ice` and `room_corpus`, load average 57–89 | **32 of 32 passed** | | repo text checks: spec refs, section refs, vanity metrics, comparative prose, tests-in-CI, orphan sources | 0 | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
str0m 0.24.1 with its pure-Rust crypto backend for WebRTC, hyper 1.11.1 for the
WHIP and WHEP resources. Every new crate in the lock is MIT, Apache-2.0 or ISC.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
SPEC §5 *Broadcast and streaming codecs* registers two sink-supplied payload
capabilities, 2026-10-18, no edition named:

- wai.video.vp8 (RFC 6386): VP8 frames in the IVF frame container, FourCC
  VP80, the framing wai.video.vp9 uses. Royalty-free, as VP9. Replicate class
  StandardUnclassified: RFC 6386's decoder-conformance text was not read, so
  the registry promises no byte identity.
- wai.audio.opus.packet (RFC 6716): one Opus packet (§3), TOC byte first, no
  container, the per-packet form a live track carries. No pre-skip or output
  gain (those are Ogg Opus's, RFC 7845 §5.1); wai.audio.opus stays the stored
  stream. Royalty-free; StandardTolerance as wai.audio.opus (RFC 6716 §6).

Both are standalone, Lossy, outside the floor and the recommended set, and
neither is an LCEVC base. The class functions, the registry table, the
classical list, the glossary links and the registry tests list both; SPEC §7's
class table, the research registry (footnote 15) and the changelog record
them. The generated exports follow in the next commit.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
`wai registry export` output: registry/capabilities.json, vocab/v2.ttl,
vocab/v2.shapes.ttl and the generated table in REGISTRY.md gain the two rows.
No hand edits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
- room: WAI live-rooms descriptors and grants, read and checked from the
  extension's text, as the gateway admits a bearer token.
- frame: one frame, one WAI1 envelope, in the payload framing of
  wai.video.avc, wai.video.vp8, wai.video.vp9, wai.video.av1 and
  wai.audio.opus.packet, with a live member: the room-clock time, the RTP
  timestamp and, on a group's first object, the grant reference.
- package: video groups at keyframes, audio groups of a fixed duration, the
  RTP-to-room-clock mapping and its bound, and each group's receipts signed
  as the first hop and chained.
- relay: the tracks published at the relay (PUBLISH), one subgroup stream a
  group, PUBLISH_DONE once every stream is acknowledged.
- whip / whep: the sessions on str0m; WHEP verifies every object's receipt
  before it forwards it, and drops and counts what fails.
- gateway: the HTTP resources (RFC 9725; draft-ietf-wish-whep-05), bearer
  tokens, 401 / 403 per RFC 6750, ETag and If-Match, the bounds.
- record: each session's signed record, the seam for live metering's
  per-hop record.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A relay with a door on the room's namespace refuses an object of any track whose
receipts never come, the catalog among them. The gateway now signs the catalog as
it signs the media, on catalog/receipts, and a WHEP session reads the catalog only
once its receipt verifies under a trusted first hop.

One b=AS line per section: some WebRTC stacks read one bandwidth line only.

The end-to-end tests (WHIP to a relay subscriber, WHEP playback, refusals, trickle
ICE and an ICE restart) and the rooms corpus test pass on the build host.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Audio groups are checked by their span on the room clock rather than counted, and
the WHEP test closes its last audio group as it closes its last video group, so
neither depends on how the host schedules the sender.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The extension binds the contribution and playback protocols of standard encoders
and players to the tracks of a room: admission by a room grant as the bearer
token, the tracks and the signed catalog, one frame as one envelope in its
capability's framing, keyframe and fixed-window groups, the live manifest member,
the RTP-to-room-clock mapping and its bound, first-hop receipts, WHEP egress and
what it verifies, the bounds, ICE, the refusals, and how an ingest relates to a
take and to metering. E-RTMP v2 and SRT are mappings only in this revision.

SPEC names it beside the live records; the changelog records it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The gate's workspace step leaves the gateway to a job of its own, as it leaves the
relay; its paths include wai/live-conformance/rooms/, which the room reader's corpus
test reads.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into joule-moq/ingest
Some checks failed
wai-pack / wai-pack · linux (pull_request) Successful in 1m24s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m16s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 15m17s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 6m42s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 12m21s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 18s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 39s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 1m45s
wai-pack / wai-pack · macos (pull_request) Successful in 1m0s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 21s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 2m40s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 25s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 50s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 2m7s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 13m16s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 13m20s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 45m53s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 10m36s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 49m13s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 50m20s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 23s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 5m23s
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
5f0b7a9558
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The endpoint traits, the in-memory mock and the QUIC transport stay where they were,
under the default feature runtime (quic implies it). Turned off, the crate is the
draft-22 codec, the carriage of objects and receipts, the receipt builder and
verifiers and the session's setup rules, with no tokio and no native dependency, so a
browser crate can build the same code for wasm32-unknown-unknown. Additive: every
dependent that takes the default features builds what it built before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A wasm-bindgen surface, byte-oriented: the client's SETUP, every request and answer a
client sends from a JSON description, framed control messages and data streams read as
their bytes arrive, subgroup streams written object by object, a publisher's signer
(each group one object, signed into a group receipt chained to the one before, written
as the receipts-track object and the media object with its WAI_RECEIPT property) and a
subscriber's verifier (receipts admitted to the joined chain under the publisher's key,
each object verified against the receipt of its position, or accounted for by
inclusion). Native tests hold the pure-Rust half.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The live-compose layout function and mixer, the WCD1 descriptor and the composition of
a room at an instant (its lifecycle replayed, its participant set, its tiles and
voices), in the JSON view live-conformance/compose states its cases in; WEV1 events
signed and read with their payloads (captions with their origin) and the MSF timeline
binding; what a page reads of a room descriptor and its grants; a Room that holds the
records a page receives, chain by chain, and verifies and composes from them; and the
WAI1 envelope packed and read. The same functions build natively: a helper binary makes
a test room (descriptor, grants, composition descriptor, relay token rules) and computes
on the host the composition a page computes in the browser.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
web/moq: the WebSocket binding's capsules, WebTransport and the binding behind one
transport interface (WebTransport first, offering moqt-22 and the relay's certificate
hash; the binding as the fallback, or forced), a MOQT session (SETUP with the token,
requests, every data stream read concurrently, GOAWAY), and the payload framings a WAI
track carries. web/room: the room client (WebCodecs publishing with receipts, verified
subscriptions, a late joiner's FETCH, the composed canvas and the mixed AudioWorklet,
the events timeline and captions with their origin, the move after GOAWAY). web/compose:
the composition corpus in the page. e2e: the relay on loopback from a test
configuration, real Chrome through Playwright with a fake camera and microphone, each
browser under memguard.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
An object whose receipts-track object reached the relay just before the receipts
subscription did never gets it live; after a second its group of the receipts track is
fetched (moq-streaming-format §4.1). e2e/package-lock.json pins the automation package
by integrity (playwright 1.60.0 and playwright-core, Apache-2.0; fsevents, MIT, optional,
its install script never run: npm ci --ignore-scripts).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
joule-moq-web e2e: the browser's executable comes from BROWSER_BIN alone
Some checks failed
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 2m3s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 15s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 1m4s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m29s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 5m51s
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Has been cancelled
8c80ef3434
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A late joiner whose live receipts anchored its chain, then fetched the track from
group 0, had group 0 refused as chain_break although group 1 links to it. Below a
join's anchor the gap has no lower end, and a group with a null parent there is the
chain's true start (jwp-receipts §3 step 3, Joining): it is now parent_not_held, held
as history, and admitted when the group above it links back to it. A null parent
outside such a gap is still a second chain head, and a fork still chain_break.

The same rule in joule-moq's ReceiptChains, wai-rs's and jwp_verify.py, the
spec text of §3 step 3 and §5.9, and two corpus vectors
(chain_join_fetch_from_group_0, chain_join_second_start_after_the_fill).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
jwp-receipts §2.7 defines a binary form for group and object receipts and for a
group's receipts together (magic WJR1): a group is its canonical bytes (§3 step 3),
its link hash computed over them as received; an object is its members in fixed
widths, its leaf_version 1 or 2 followed by its class. Reading yields the JSON
form's members, and every rule of §3 applies to them unchanged. §2.7.4 says what
does not read (malformed, at its §3 step); §5.12 the vectors.

moq-streaming-format §4.1: a receipts-track object carries that form.

wai-rs: jwp_delivery::binary (reader and writer, through from_json), and 20
binary vectors in jwp-conformance; jwp_verify.py reads and writes them from the
text. joule-moq: GroupReceipt/ObjectReceipt to_binary/from_binary, read_receipts,
ReceiptBundle::to_payload/from_payload in the binary form (and read, with the link
hash over the bytes received); the JWP_OBJECT_RECEIPT property carries an object
receipt standing alone. The JSON payload is not read. joule-moq-web's verifier
measures each receipts object, and the same receipts in the JSON form.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
§6.1 names a media track's receipts track (its namespace, its name followed by
_receipts) and a publisher's catalog track (the room and owner fields, Track Name
catalog), every byte still in a-z, 0-9 and _; §6.2 ties the catalog's wai_receipts_track
to it. §10.2 adds the scopes: the publisher's and each slot's catalog track, and for a
subscriber every catalog and each layer's receipts track. wai-rs (RECEIPTS_SUFFIX,
CATALOG_TRACK, receipts_track_name, catalog_namespace, moqt_scopes) and
live_rooms_verify.py agree.

The browser client and the relay use those names: the receipts tracks are 0_receipts,
and joule-moq-relay's receipts_track_suffix defaults to _receipts.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A grant is public, so no binding takes it as a bearer credential. §10.5 requires a
WRP1 proof record beside every grant: the grant's digest, the principal's key, the
verifier's fresh challenge and a context (a verb and the //authority/path the client
addressed), signed by the principal. Over MOQT the relay's SETUP carries the
challenge (GRANT_CHALLENGE, 0x3A47), sent at once, and the client's SETUP the grant
as its AUTHORIZATION TOKEN and the proof (GRANT_PROOF, 0x3A49); a SETUP whose grant
has no proof that passes is closed UNAUTHORIZED. Over HTTP (WHIP, WHEP) the grant is
the Bearer token, the proof the WAI-Room-Proof header, and the challenge comes in
WAI-Room-Challenge on a 401 and on each accepted response, single-use. §10.5.4 gives
the seven checks and their reasons (pop_missing to pop_wrong_context, in §11); the
CAT mapping and the security text say so.

wai-rs: Proof (sign, read), check_proof, grant_digest_and_principal, base64url;
the rooms corpus gains 17 proofs (MOQT and HTTP, good, replayed, wrong key, forged,
another session's challenge, another grant, another endpoint or method, a missing
proof and unreadable records). live_rooms_verify.py checks them from the text.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
joule-moq: grant_proof (the WRP1 record, check_proof, the SETUP options and helpers,
grant_digest_and_principal, base64url) and a strict Ed25519 check shared with the
receipts. The relay sends its SETUP first with a fresh GRANT_CHALLENGE, checks every
grant its rules mark proof against the client's GRANT_PROOF before serving any
request, and closes a session that presents one without a proof that passes with
UNAUTHORIZED; the transports give it the target the client addressed. Its test client
presents grants with proofs; tests/grant_proof.rs. The web client presents its grant
with a proof signed in wasm, the per-participant secret gone.

Not yet built or run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Under load (e2e --runs 4, host load near 100) one run's page met a VP9 decoding
error and never decoded that track again: WebCodecs closes a codec that errs, and
the page went on handing frames to the closed decoder. Each group rides a stream of
its own and waits for its receipts, so a group can be released after a later one;
decoded then it corrupts the decoder's references. The page now drops such a group
(counted as late_dropped), and makes a decoder again after an error, the video
waiting for its next key frame (decoder_restarts).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
live-rooms §10.5.3: cite RFC 9449's resource-server nonce (§9), and RFC 9725 without sections
Some checks failed
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
wai-pack / wai-pack · does this change touch it (pull_request) Has been cancelled
wai-pack / wai-pack · linux (pull_request) Has been cancelled
wai-pack / wai-pack · linux-arm64 (pull_request) Has been cancelled
wai-pack / wai-pack · macos (pull_request) Has been cancelled
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been cancelled
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been cancelled
wai-pack / wai-pack · dependency licences (pull_request) Has been cancelled
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been cancelled
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been cancelled
wai-web wasm32 / does this change touch it (pull_request) Has been cancelled
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been cancelled
a70edf764a
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into joule-moq/harden
Some checks failed
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
a8b02f7284
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#327 (proof of possession, the receipts' binary form, room track names), with #324
and main beneath it. The workspace keeps both new members; the changelog keeps
every entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Gateway: a challenge store (issued per endpoint or session, consumed, kept for
replay detection), joule_moq::grant_proof::check_proof on every request that
carries a grant (POST, and PATCH, DELETE, GET and HEAD on session URLs, GET and
HEAD on endpoints when a grant is carried), a fresh WAI-Room-Challenge on every
response to such a request, the two fields in CORS, admission's single-use check
only on a request that opens a session. Receipts tracks renamed *_receipts.

Tests: an Auth helper that proves possession; whip_publish, whep_play and codecs
moved onto it. auth_and_ice.rs is still the pre-proof version and does not build
against the new helpers; room_corpus does not yet run the corpus's proofs.
Not built, not tested, not pushed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
check_http_proof is authorize()'s proof step as a function (live-rooms §10.5.3, §10.5.4).
room_corpus runs the corpus's six HTTP proofs through it with the gateway's own reading of
each grant, and presents each accepted proof again as a replay. auth_and_ice presents
grants with proofs through the Auth helper, and shows the session's PATCH and DELETE
refusing a grant presented alone.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
live-ingest: the proof of possession at admission (§3.1, §3.2 step 7, §3.4), the _receipts names, its bounds and reasons
All checks were successful
wai-quantum OpenCL member / member · OpenCL GPU (macos) (pull_request) Has been skipped
wai-quantum OpenCL member / member · OpenCL absent (linux) (pull_request) Has been skipped
wai-quantum on every processor / emulated · s390x-unknown-linux-gnu (pull_request) Successful in 3m52s
wai-quantum-qpu / does this change touch it (pull_request) Successful in 10s
wai-quantum-qpu / clients against their contracts (linux) (pull_request) Has been skipped
wai-quantum service / does this change touch it (pull_request) Successful in 10s
wai-quantum matrix-extension member / does this change touch it (pull_request) Successful in 9s
wai-quantum service / service · linux (pull_request) Has been skipped
wai-quantum service / service · macos (pull_request) Has been skipped
wai-quantum matrix-extension member / member · matrix extension, emulated (aarch64 Linux) (pull_request) Has been skipped
wai-quantum matrix-extension member / member · matrix extension absent (macos) (pull_request) Has been skipped
wai-quantum portable tensor program / does this change touch it (pull_request) Successful in 8s
wai-quantum portable tensor program / tensor program · CPU, mutants, GPU builds (pull_request) Has been skipped
wai-quantum portable tensor program / tensor program · GPUs (jetson-hub) (pull_request) Has been skipped
wai-quantum portable tensor program / tensor program · integrated GPU (framework-128gb) (pull_request) Has been skipped
wai-quantum portable tensor program / tensor program · Metal and the arm64 CPU (macos) (pull_request) Has been skipped
wai-quantum tensor-engine member / does this change touch it (pull_request) Successful in 9s
wai-quantum CPU vector units / does this change touch it (pull_request) Successful in 7s
wai-quantum tensor-engine member / member · tensor engine, the kernel toolchain's simulator (linux) (pull_request) Has been skipped
wai-quantum tensor-engine member / member · tensor engine absent (linux-arm64) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit, rvv 1024-bit (riscv64, emulated) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit, sve 128-bit (aarch64, emulated) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit, rvv 128-bit (riscv64, emulated) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit, sve 2048-bit (aarch64, emulated) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit, avx2 (x86_64 silicon) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit, sve 512-bit (aarch64, emulated) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector units, avx2 and avx512 (framework-128gb silicon) (pull_request) Has been skipped
wai-quantum CPU vector units / member · vector unit absent (macos) (pull_request) Has been skipped
wai-quantum on every processor / emulated · sparc64-unknown-linux-gnu (pull_request) Successful in 6m4s
wai-quantum on every processor / emulated · i686-unknown-linux-gnu (pull_request) Successful in 3m59s
caccfd86b3
The gateway's README and both CHANGELOGs say the same.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The relay's session setup now proves every room grant (live-rooms §10.5) and then opens
the session's tally on the room's metered grant (live-metering). REGISTRY lists WJR1,
WMH1 and WMR1; the CHANGELOG keeps both entries; capabilities.json is regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
wai registry export: WJR1 and the metering records together
Some checks failed
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been cancelled
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been cancelled
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been cancelled
WAI energy receipts / does this change touch it (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux (pull_request) Has been cancelled
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Has been cancelled
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / does this change touch it (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (pull_request) Has been cancelled
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Has been cancelled
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Has been cancelled
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Has been cancelled
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Has been cancelled
WAI conformance / cross-arch byte-equality · macos (pull_request) Has been cancelled
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Has been cancelled
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Has been cancelled
standards workspaces build + test / which workspaces this change touches (pull_request) Has been cancelled
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (pull_request) Has been cancelled
46dbd1a669
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge joule-moq/harden (#327, now with main and metering) into joule-moq/ingest
Some checks failed
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Successful in 3m12s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 18m23s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 18s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 2m49s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 11s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 28s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m31s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 3m59s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Failing after 7m21s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 15m53s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 17m23s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 3m28s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Successful in 1h3m59s
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Successful in 1h5m54s
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Successful in 1h7m50s
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Successful in 12m11s
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Successful in 18s
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Successful in 5m13s
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Successful in 12m30s
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Successful in 1m44s
wai-pack / wai-pack · linux-arm64 (pull_request) Successful in 1m51s
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Successful in 11m3s
wai-pack / wai-pack · the floor through a media prober (pull_request) Successful in 44s
wai-pack / wai-pack · dependency licences (pull_request) Successful in 2m6s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 23m45s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 32s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 1m12s
wai-pack / wai-pack · independent verifier (Python) (pull_request) Successful in 6m44s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 9m9s
wai-pack / wai-pack · macos (pull_request) Successful in 1m49s
7181b8a00a
SPEC §5 keeps live-metering's paragraph and then live-ingest's; joule-code's CHANGELOG keeps
both entries, the gateway's first.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Transaction-Science/open-standards!325
No description provided.