joule-moq: draft-ietf-moq-transport-18 and -16 beside -22, and interoperability with other implementations' relays #401

Closed
dcharlot wants to merge 0 commits from joule-moq/multi-draft into main
Owner

What this is

joule-moq speaks draft-ietf-moq-transport-18 and draft-ietf-moq-transport-16 beside -22 (still the default), joule-moq-relay serves sessions at all three, and a new joule-moq-interop runs both against other implementations. The results of 2026-10-21 are in joule-moq's README, Interoperability, and summarised below.

joule-moq

  • draft18: -18's own control messages, parameters (SUBSCRIPTION_FILTER, FETCH types with an exclusive end, GOAWAY with a Request ID, PUBLISH_BLOCKED, PUBLISH_OK's parameters), codes and Setup Options; -22's integer, Key-Value-Pairs, names, token, frame and object codecs, which -18 shares.
  • draft16: its own throughout (RFC 9000 integers, one bidirectional control stream with CLIENT_SETUP/SERVER_SETUP, MAX_REQUEST_ID and REQUESTS_BLOCKED, the cancel messages, KVP Message Parameters, Subscribe Options, absolute fetch IDs, no FIRST_OBJECT, 1–32 namespace fields).
  • draft::Draft chooses per session by ALPN moqt-NN or WT-Available-Protocols, preferring -22, then -18, then -16.
  • draft18::bridge and draft16::bridge map every message and object to and from the -22 model, and name what cannot cross: refused with REQUEST_ERROR NOT_SUPPORTED or INVALID_RANGE, or left out and noted. The README tables each difference.
  • Vector corpora tests/vectors/draft-18 and draft-16. The crate regenerates and checks them, and a decoder written from each draft's text reads them (moqt18_check.py, moqt16_check.py). They include the drafts' own examples: -18 Table 2, RFC 9000 A.1, and the first subgroup of -18 §11.6 and of -16 §10.5. The working group publishes no corpus of its own.

joule-moq-relay

  • transport::bridge presents an -18 or -16 session to the relay as a -22 one:
    • at -18, stream by stream;
    • at -16, it gives each request on the control stream a virtual stream, renumbers Request IDs, grants MAX_REQUEST_ID, and turns a cancelled stream into UNSUBSCRIBE, FETCH_CANCEL, PUBLISH_NAMESPACE_DONE or PUBLISH_NAMESPACE_CANCEL.
  • Each message in and out is logged at debug level.
  • The client takes a draft, and its WebTransport side takes a TLS configuration.

Faults fixed, all found by the interoperability runs:

  • A PUBLISH_DONE before the streams it counts (-22 §9.9: request and data streams are not ordered). The relay waits until it has read as many data streams as the Stream Count says, or 3 s. It ends at once when the count is 0 or unknown, or when the publisher's session is gone.
  • A track published again after TRACK_ENDED is a new instance: the ended one's cache, Largest Location and subgroup owners go. A DVR-kept track is kept whole.
  • A publisher that closes its control stream as it leaves (-18 §3.3 makes that a protocol violation). The session now closes once that publisher's tracks have ended and its streams have been read, or after 3 s. Its tracks end with the PUBLISH_DONE status already read, not GOING_AWAY: session clean-up no longer races the request stream.
  • At -18, a requester's FIN on PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS cancels it (§6.1, §6.2). At -22 a FIN is not a cancellation (§6.4.2.2). The bridge maps the one to the other, and withholds a -22 FIN on these from an -18 peer.
  • At -16, a Request ID that arrives ahead of an earlier one still in flight on another stream (SUBSCRIBE_NAMESPACE rides its own, §9.25) is admitted. -16 §9.1 asks for "the next in sequence", which cannot hold across QUIC streams. An ID seen before still closes the session with INVALID_REQUEST_ID.

joule-moq-interop (new; publish = false)

  • It runs the interoperability runner's test cases through the runner's test-client interface (TAP 14, RELAY_URL/TESTCASE/TLS_DISABLE_VERIFY).
  • wai-receipts publishes a catalog, a WAI track (envelopes with their receipts in Immutable Properties) and its receipts track through a relay. A second session verifies every object against its receipt: inclusion, signature, the receipts chain, and the receipt and envelope the object carries.
  • --tamper payload|receipt|in-flight, --flow publish and --no-object-properties are the diagnostic options.
  • Scripts:
    • scripts/public.py runs it against the registry's relays;
    • scripts/clients.sh runs other implementations' test clients against joule-moq-relay;
    • scripts/local.sh runs on loopback.
  • None of these runs is in CI: they reach the public internet or run other implementations' code.

Docs and CI

  • The joule-moq and joule-moq-relay READMEs; wai/extensions/moq-streaming-format.md §6 says which drafts the reference speaks.
  • CHANGELOG entries for joule-code and wai.
  • joule-code-conformance.yml runs the -18 and -16 independent decoders beside -22's.

Interoperability matrix, 2026-10-21

The runner's registry is at commit e8d544e9. "Runner" means the runner's test cases, and "receipts" means wai-receipts. Both were run over QUIC and WebTransport unless noted.

Relay (by capability) Draft Result Cause
A public MOQT relay network's draft-18 interop endpoint -18 pass
The same network's production endpoint -16 not covered needs an operator token
The same network's draft-14 endpoint -14 not covered no -14 here
An open interop relay, -14 to -22 on one port -22, -18, -16 pass
A research relay at -18 / the same at -22 -18 / -22 pass
A relay at -22 -22 pass
The same project's relay at -18 -18 fail SUBSCRIBE in a published namespace (or after PUBLISH) gets no answer; no NAMESPACE; rendezvous DOES_NOT_EXIST
A relay at -16 and -18 -18, -16 pass
A relay at -14 to -22 -22, -18, -16 pass
A media server's relay at -22 -22 partial rendezvous-timeout answered DOES_NOT_EXIST; receipts pass
A relay library's relay, -16 to -22 all three partial receipts pass everywhere; a closed session's namespace stays registered ("Namespace already published") for the next session
An open relay network's anonymous endpoint -18, -16 partial Object Properties dropped: every object verifies against the receipts track but carries no receipt of its own; PUBLISH refused; rendezvous DOES_NOT_EXIST; all runner cases pass at -16
A relay at -18 to -22 -18 partial as the row above
The same relay -22 fail refuses the publisher's SUBSCRIBE_OK (MALFORMED_TRACK "short buffer")
A relay at -18 with a self-signed certificate -18 partial objects with Object Properties not forwarded (receipts track arrives and verifies; without per-object properties every object verifies); SUBSCRIBE_OK for unpublished tracks
A relay at -18 over WebTransport only -18 fail not reachable from the build host (no QUIC handshake answer)

Other implementations' test clients against joule-moq-relay on the build host's loopback, over QUIC and WebTransport, three rounds:

Client Draft Result Cause
The public relay network's -18 test client (runner cases and data-plane cases) -18 pass, all rounds
An open relay network's test client, draft pinned -18, -16 pass, all rounds
The same client at its default -22 partial its SUBSCRIBE carries Message Parameter 0x1, which -22 does not define (§9.20: PROTOCOL_VIOLATION); it withdraws a namespace by FIN, which -22 §6.4.2.2 says is not a cancellation

Checks (build host, through rb, at the merged head)

Command Result
cargo test --locked -p joule-moq --features quic --all-targets exit 0
cargo test --locked -p joule-moq --features secure-objects --lib secure_objects / --test secure_objects_vectors exit 0 / exit 0
cargo test --locked -p joule-moq-relay --all-targets exit 0
cargo test --locked -p joule-moq-interop --all-targets exit 0
cargo test --locked -p joule-moq-ingest --all-targets, -p joule-moq-web --features e2ee --lib, -p joule-moq-web --lib, -p joule-moq-mls, -p lockstep-quic exit 0 each
cargo build --locked -p joule-moq-web --target wasm32-unknown-unknown exit 0
cargo build --locked in crates/joule-moq-room; cargo test --locked in examples/transport-moqt exit 0 / exit 0
cargo metadata --locked --offline --format-version 1 for joule-code-rs, crates/joule-moq-room, examples/transport-moqt exit 0 each (one lockfile changed, additions only)
cargo clippy --locked --all-targets on joule-moq (quic, secure-objects), joule-moq-relay, joule-moq-interop, joule-moq-ingest no note on this branch's code; the notes left are on lines unchanged from main
publish_done_streams and drafts, looped 10 times 10 of 10 pass
python3 -I .../draft-18/moqt18_check.py, .../draft-16/moqt16_check.py, .../draft-22/moqt22_check.py 0 disagree each
scripts/local.sh exit 0: every draft and cross-draft pairing passes; each tamper mode refused
repo checks: check-tests-in-ci.py, check-workflow-concurrency.sh, check-spec-refs.py, check-wai-section-refs.py, check-no-vanity-metrics.sh, test-public-text-checks.sh, check-comparative-prose.sh, check-orphan-sources.py all clean

Forge CI at f52a0c70e (before the last merge of main, which changed nothing under joule-code/): the joule-code conformance gate (C1–C4 and the -22, -18 and -16 decoders; joule-moq-relay; joule-moq-ingest), joule-moq-room, joule-moq-web with its browser end-to-end, the joule-code-rs workspace build and the repository checks all passed. One job failed: macOS energy backends. Its failure is jcp-gateway's tests/upstream_tree.rs (a_declared_server_holding_a_gpu_client_is_never_sealed_whole: "gpu not among [gpu-unknown]"), a GPU-energy reading on the shared runner, in a crate this branch does not touch.

Falsifications (each run after a commit, restored after)

Broken on purpose Result
-18 SUBSCRIBER_PRIORITY written as a varint draft18_vectors exit 101; moqt18_check.py "1 disagree", exit 1
-16 AbsoluteRange End Group written as a delta draft16_vectors exit 101; moqt16_check.py "1 disagree", exit 1
wai-receipts --tamper payload / receipt / in-flight each exit 1 (an object refused; signature and chain broken; objects altered in flight refused)
The PUBLISH_DONE stream wait disabled publish_done_streams exit 101 (the object after PUBLISH_DONE lost)
The restart-after-TRACK_ENDED rule disabled exit 101 (the ended instance's Largest Location given)
-18 FIN-as-cancellation disabled / -22 FIN withholding disabled drafts exit 101 / exit 101
The control-stream-closed grace disabled drafts exit 101 (the session closed under the last object)
The early end of the stream wait at a session's end disabled publish_done_streams exit 101 (ended after 2.8 s, not at the close)
-16 strict Request ID sequence restored drafts exit 101 (a_raw_16_session)

Decisions

  • -22 stays the model, and the earlier drafts are bridged at the session's edge, so the relay and receipts code is written once.
  • The bridge refuses or leaves out, by name, whatever the target draft cannot say, and never invents a value.
  • The WebTransport client offers one draft per session: the vendored WebTransport layer does not show the client the response's WT-Protocol.
  • Where a draft's text cannot hold across QUIC streams (-16's strict Request ID sequence), or leaves FIN semantics to cancellation (-18), the README states this end's reading.
  • Public relays are named by capability only. The runner's Docker harness was not used: its clients were built natively on the build host, and joule-moq-interop implements its test-client interface.

Not done

  • draft-ietf-moq-transport-14 and the drafts between -16 and -22 other than -18.
  • The public relay network's production -16 endpoint: it needs an operator token.
  • The runner's publish-subscribe-media case (it needs a specific media player), and the data-plane cases on this side's client.
  • Unrelated clippy notes that predate this branch are left as they are.

🤖 Generated with Claude Code


Closed: Landed in #428 (merge train, merged as aa61ab3b with all 65 checks green).

## What this is `joule-moq` speaks `draft-ietf-moq-transport-18` and `draft-ietf-moq-transport-16` beside `-22` (still the default), `joule-moq-relay` serves sessions at all three, and a new `joule-moq-interop` runs both against other implementations. The results of 2026-10-21 are in `joule-moq`'s README, *Interoperability*, and summarised below. ### `joule-moq` - `draft18`: -18's own control messages, parameters (SUBSCRIPTION_FILTER, FETCH types with an exclusive end, GOAWAY with a Request ID, PUBLISH_BLOCKED, PUBLISH_OK's parameters), codes and Setup Options; -22's integer, Key-Value-Pairs, names, token, frame and object codecs, which -18 shares. - `draft16`: its own throughout (RFC 9000 integers, one bidirectional control stream with CLIENT_SETUP/SERVER_SETUP, MAX_REQUEST_ID and REQUESTS_BLOCKED, the cancel messages, KVP Message Parameters, Subscribe Options, absolute fetch IDs, no FIRST_OBJECT, 1–32 namespace fields). - `draft::Draft` chooses per session by ALPN `moqt-NN` or `WT-Available-Protocols`, preferring -22, then -18, then -16. - `draft18::bridge` and `draft16::bridge` map every message and object to and from the -22 model, and name what cannot cross: refused with REQUEST_ERROR NOT_SUPPORTED or INVALID_RANGE, or left out and noted. The README tables each difference. - Vector corpora `tests/vectors/draft-18` and `draft-16`. The crate regenerates and checks them, and a decoder written from each draft's text reads them (`moqt18_check.py`, `moqt16_check.py`). They include the drafts' own examples: -18 Table 2, RFC 9000 A.1, and the first subgroup of -18 §11.6 and of -16 §10.5. The working group publishes no corpus of its own. ### `joule-moq-relay` - `transport::bridge` presents an -18 or -16 session to the relay as a -22 one: - at -18, stream by stream; - at -16, it gives each request on the control stream a virtual stream, renumbers Request IDs, grants MAX_REQUEST_ID, and turns a cancelled stream into UNSUBSCRIBE, FETCH_CANCEL, PUBLISH_NAMESPACE_DONE or PUBLISH_NAMESPACE_CANCEL. - Each message in and out is logged at debug level. - The client takes a draft, and its WebTransport side takes a TLS configuration. **Faults fixed, all found by the interoperability runs:** - **A PUBLISH_DONE before the streams it counts** (-22 §9.9: request and data streams are not ordered). The relay waits until it has read as many data streams as the Stream Count says, or 3 s. It ends at once when the count is 0 or unknown, or when the publisher's session is gone. - **A track published again after TRACK_ENDED** is a new instance: the ended one's cache, Largest Location and subgroup owners go. A DVR-kept track is kept whole. - **A publisher that closes its control stream as it leaves** (-18 §3.3 makes that a protocol violation). The session now closes once that publisher's tracks have ended and its streams have been read, or after 3 s. Its tracks end with the PUBLISH_DONE status already read, not GOING_AWAY: session clean-up no longer races the request stream. - **At -18, a requester's FIN on PUBLISH_NAMESPACE, SUBSCRIBE_NAMESPACE or SUBSCRIBE_TRACKS cancels it** (§6.1, §6.2). At -22 a FIN is not a cancellation (§6.4.2.2). The bridge maps the one to the other, and withholds a -22 FIN on these from an -18 peer. - **At -16, a Request ID that arrives ahead of an earlier one** still in flight on another stream (SUBSCRIBE_NAMESPACE rides its own, §9.25) is admitted. -16 §9.1 asks for "the next in sequence", which cannot hold across QUIC streams. An ID seen before still closes the session with INVALID_REQUEST_ID. ### `joule-moq-interop` (new; `publish = false`) - It runs the interoperability runner's test cases through the runner's test-client interface (TAP 14, `RELAY_URL`/`TESTCASE`/`TLS_DISABLE_VERIFY`). - `wai-receipts` publishes a catalog, a WAI track (envelopes with their receipts in Immutable Properties) and its receipts track through a relay. A second session verifies every object against its receipt: inclusion, signature, the receipts chain, and the receipt and envelope the object carries. - `--tamper payload|receipt|in-flight`, `--flow publish` and `--no-object-properties` are the diagnostic options. - Scripts: - `scripts/public.py` runs it against the registry's relays; - `scripts/clients.sh` runs other implementations' test clients against `joule-moq-relay`; - `scripts/local.sh` runs on loopback. - None of these runs is in CI: they reach the public internet or run other implementations' code. ### Docs and CI - The `joule-moq` and `joule-moq-relay` READMEs; `wai/extensions/moq-streaming-format.md` §6 says which drafts the reference speaks. - CHANGELOG entries for joule-code and wai. - `joule-code-conformance.yml` runs the -18 and -16 independent decoders beside -22's. ## Interoperability matrix, 2026-10-21 The runner's registry is at commit `e8d544e9`. "Runner" means the runner's test cases, and "receipts" means `wai-receipts`. Both were run over QUIC and WebTransport unless noted. | Relay (by capability) | Draft | Result | Cause | |---|---|---|---| | A public MOQT relay network's draft-18 interop endpoint | -18 | **pass** | | | The same network's production endpoint | -16 | not covered | needs an operator token | | The same network's draft-14 endpoint | -14 | not covered | no -14 here | | An open interop relay, -14 to -22 on one port | -22, -18, -16 | **pass** | | | A research relay at -18 / the same at -22 | -18 / -22 | **pass** | | | A relay at -22 | -22 | **pass** | | | The same project's relay at -18 | -18 | fail | SUBSCRIBE in a published namespace (or after PUBLISH) gets no answer; no NAMESPACE; rendezvous DOES_NOT_EXIST | | A relay at -16 and -18 | -18, -16 | **pass** | | | A relay at -14 to -22 | -22, -18, -16 | **pass** | | | A media server's relay at -22 | -22 | partial | rendezvous-timeout answered DOES_NOT_EXIST; receipts pass | | A relay library's relay, -16 to -22 | all three | partial | receipts pass everywhere; a closed session's namespace stays registered ("Namespace already published") for the next session | | An open relay network's anonymous endpoint | -18, -16 | partial | Object Properties dropped: every object verifies against the receipts track but carries no receipt of its own; PUBLISH refused; rendezvous DOES_NOT_EXIST; all runner cases pass at -16 | | A relay at -18 to -22 | -18 | partial | as the row above | | The same relay | -22 | fail | refuses the publisher's SUBSCRIBE_OK (MALFORMED_TRACK "short buffer") | | A relay at -18 with a self-signed certificate | -18 | partial | objects with Object Properties not forwarded (receipts track arrives and verifies; without per-object properties every object verifies); SUBSCRIBE_OK for unpublished tracks | | A relay at -18 over WebTransport only | -18 | fail | not reachable from the build host (no QUIC handshake answer) | Other implementations' test clients against `joule-moq-relay` on the build host's loopback, over QUIC and WebTransport, three rounds: | Client | Draft | Result | Cause | |---|---|---|---| | The public relay network's -18 test client (runner cases and data-plane cases) | -18 | **pass**, all rounds | | | An open relay network's test client, draft pinned | -18, -16 | **pass**, all rounds | | | The same client at its default | -22 | partial | its SUBSCRIBE carries Message Parameter 0x1, which -22 does not define (§9.20: PROTOCOL_VIOLATION); it withdraws a namespace by FIN, which -22 §6.4.2.2 says is not a cancellation | ## Checks (build host, through `rb`, at the merged head) | Command | Result | |---|---| | `cargo test --locked -p joule-moq --features quic --all-targets` | exit 0 | | `cargo test --locked -p joule-moq --features secure-objects --lib secure_objects` / `--test secure_objects_vectors` | exit 0 / exit 0 | | `cargo test --locked -p joule-moq-relay --all-targets` | exit 0 | | `cargo test --locked -p joule-moq-interop --all-targets` | exit 0 | | `cargo test --locked -p joule-moq-ingest --all-targets`, `-p joule-moq-web --features e2ee --lib`, `-p joule-moq-web --lib`, `-p joule-moq-mls`, `-p lockstep-quic` | exit 0 each | | `cargo build --locked -p joule-moq-web --target wasm32-unknown-unknown` | exit 0 | | `cargo build --locked` in `crates/joule-moq-room`; `cargo test --locked` in `examples/transport-moqt` | exit 0 / exit 0 | | `cargo metadata --locked --offline --format-version 1` for `joule-code-rs`, `crates/joule-moq-room`, `examples/transport-moqt` | exit 0 each (one lockfile changed, additions only) | | `cargo clippy --locked --all-targets` on joule-moq (quic, secure-objects), joule-moq-relay, joule-moq-interop, joule-moq-ingest | no note on this branch's code; the notes left are on lines unchanged from main | | `publish_done_streams` and `drafts`, looped 10 times | 10 of 10 pass | | `python3 -I .../draft-18/moqt18_check.py`, `.../draft-16/moqt16_check.py`, `.../draft-22/moqt22_check.py` | 0 disagree each | | `scripts/local.sh` | exit 0: every draft and cross-draft pairing passes; each tamper mode refused | | repo checks: `check-tests-in-ci.py`, `check-workflow-concurrency.sh`, `check-spec-refs.py`, `check-wai-section-refs.py`, `check-no-vanity-metrics.sh`, `test-public-text-checks.sh`, `check-comparative-prose.sh`, `check-orphan-sources.py` | all clean | Forge CI at `f52a0c70e` (before the last merge of main, which changed nothing under `joule-code/`): the joule-code conformance gate (C1–C4 and the -22, -18 and -16 decoders; `joule-moq-relay`; `joule-moq-ingest`), `joule-moq-room`, `joule-moq-web` with its browser end-to-end, the `joule-code-rs` workspace build and the repository checks all passed. One job failed: *macOS energy backends*. Its failure is `jcp-gateway`'s `tests/upstream_tree.rs` (`a_declared_server_holding_a_gpu_client_is_never_sealed_whole`: "gpu not among [gpu-unknown]"), a GPU-energy reading on the shared runner, in a crate this branch does not touch. ## Falsifications (each run after a commit, restored after) | Broken on purpose | Result | |---|---| | -18 SUBSCRIBER_PRIORITY written as a varint | `draft18_vectors` exit 101; `moqt18_check.py` "1 disagree", exit 1 | | -16 AbsoluteRange End Group written as a delta | `draft16_vectors` exit 101; `moqt16_check.py` "1 disagree", exit 1 | | `wai-receipts --tamper payload` / `receipt` / `in-flight` | each exit 1 (an object refused; signature and chain broken; objects altered in flight refused) | | The PUBLISH_DONE stream wait disabled | `publish_done_streams` exit 101 (the object after PUBLISH_DONE lost) | | The restart-after-TRACK_ENDED rule disabled | exit 101 (the ended instance's Largest Location given) | | -18 FIN-as-cancellation disabled / -22 FIN withholding disabled | `drafts` exit 101 / exit 101 | | The control-stream-closed grace disabled | `drafts` exit 101 (the session closed under the last object) | | The early end of the stream wait at a session's end disabled | `publish_done_streams` exit 101 (ended after 2.8 s, not at the close) | | -16 strict Request ID sequence restored | `drafts` exit 101 (a_raw_16_session) | ## Decisions - -22 stays the model, and the earlier drafts are bridged at the session's edge, so the relay and receipts code is written once. - The bridge refuses or leaves out, by name, whatever the target draft cannot say, and never invents a value. - The WebTransport client offers one draft per session: the vendored WebTransport layer does not show the client the response's `WT-Protocol`. - Where a draft's text cannot hold across QUIC streams (-16's strict Request ID sequence), or leaves FIN semantics to cancellation (-18), the README states this end's reading. - Public relays are named by capability only. The runner's Docker harness was not used: its clients were built natively on the build host, and `joule-moq-interop` implements its test-client interface. ## Not done - `draft-ietf-moq-transport-14` and the drafts between -16 and -22 other than -18. - The public relay network's production -16 endpoint: it needs an operator token. - The runner's `publish-subscribe-media` case (it needs a specific media player), and the data-plane cases on this side's client. - Unrelated clippy notes that predate this branch are left as they are. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- **Closed:** Landed in #428 (merge train, merged as aa61ab3b with all 65 checks green).
The Draft enum speaks -22 (default), -18 and -16 by ALPN. -18 is a module of
its own: its Setup Options, Message Parameters (SUBSCRIPTION_FILTER),
control messages (GOAWAY's Request ID, FETCH types with an exclusive end,
PUBLISH_BLOCKED, Table 5's PUBLISH_OK), codes, and the data plane, which is
-22's but for End of Timed-Out Range. draft18::bridge maps each message to
and from -22's model and names what one draft cannot say.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
-16 writes every field with QUIC's integer (RFC 9000 §16), carries every
request and response on one bidirectional control stream by Request ID,
encodes Message Parameters as Key-Value-Pairs, and numbers some codes
differently (PUBLISH_DONE EXPIRED/TOO_FAR_BEHIND, stream reset
UNKNOWN_OBJECT_STATUS). Its module is its own; draft16::bridge maps each
message and object to and from -22, re-encoding the pairs inside
Immutable Extensions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The relay offers moqt-22, moqt-18 and moqt-16 by ALPN and by
WT-Available-Protocols, and transport::bridge puts a session of another
draft behind its -22 interface: -18 stream by stream, -16 by routing
each request of its one control stream to a stream of its own (Request ID
renumbering, MAX_REQUEST_ID credit, cancel messages). The client side is
the same wrap. tests/drafts.rs runs the two-publisher scenario with WAI
receipts at -18, at -16 and across mixed drafts, FETCH from the cache at
-18 and -16, a refusal by name, and raw -18 and -16 sessions.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Draft::select takes the most preferred draft a client offers (-22 first),
as the relay's TLS does. The relay client takes extra Setup Options
(AUTHORITY and PATH for a moqt:// URI), and the relay tests can run any
session at -18 or -16.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A client for any MOQT relay at -22, -18 or -16, with the runner's
test-client interface (TAP 14) and its draft-18 test cases, and
wai-receipts: a WAI track, its receipts track and a catalog through the
relay, every object verified at the subscriber against the publisher's
signed receipts, with --tamper payload|receipt|in-flight to show the check
fails. scripts/local.sh runs it against joule-moq-relay on loopback.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
tests/draft18_vectors.rs and tests/draft16_vectors.rs generate each draft's
corpus (every control message, filters, FETCH types, streams, datagrams,
the drafts' own examples, and negative vectors for what each draft
refuses that a later one reads). moqt18_check.py and moqt16_check.py
decode them from the drafts' text alone. The READMEs table every
difference; moq-streaming-format says which drafts the reference speaks.
joule-moq-interop: the PUBLISH flow, WebTransport without verification,
and inclusion and property survival reported apart.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A publisher's PUBLISH_DONE and its data streams are not ordered with each other
(draft-ietf-moq-transport-22 §9.9). The relay ended the track downstream on the
message and dropped the alias, so a stream still in flight was lost: an -18
test client over WebTransport saw PUBLISH_DONE and no object. The relay now
counts each upstream data stream as its reading ends and, after PUBLISH_DONE,
waits until it has read the count, or three seconds; 0 and the unknown count
end at once. publish_done_streams tests the order at -22, -18 (QUIC and
WebTransport) and -16, a count no stream meets, and 0 and 2^64-1.

The -18 and -16 bridges log each message in and out at debug level, and the
interop client answers requests a namespace-only publisher is sent (a
TRACK_STATUS probe) with DOES_NOT_EXIST. scripts/clients.sh runs other
implementations' interop test clients against the relay on loopback;
public.py keeps each run's debug log.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
A publisher that ends its track (PUBLISH_DONE TRACK_ENDED) and a second that
publishes the same track afresh from group 0: the relay kept the first
instance's cache, its Largest Location and which upstream each subgroup came
from, so the second's group 0 was dropped as a duplicate and the subscriber was
told the first's Largest Location. An interop test client that runs a
publish-only test and then a publish-and-subscribe test on the same track met
exactly this. When nobody publishes a track its publisher ended, a new upstream
now starts it clean; a track the DVR store keeps is kept whole, and a track
whose publisher's session dropped (no TRACK_ENDED) keeps its cache.

An upstream's PUBLISH_DONE status is kept while the relay waits for its last
streams, so a session that closes in that wait ends the track with what the
publisher said; and upstream_gone ends a track only for a session that
published it, so the second call for one session is a no-op.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
-18 cancels SUBSCRIBE_NAMESPACE and SUBSCRIBE_TRACKS by "closing the stream
with either a FIN or RESET_STREAM" (§6.1) and withdraws PUBLISH_NAMESPACE by
cancelling the request (§6.2); it has no FIN that leaves a request standing.
-22 has one (§6.4.2.2). An -18 test client that withdraws its namespace with a
FIN saw no NAMESPACE_DONE through the relay. The -18 bridge now reads an -18
requester's FIN on those three requests as a cancellation, and withholds a -22
requester's FIN on them from an -18 peer, which would read it as one. Tested
with an -18 session written from -18's messages, a -22 publisher and a -22
client over -18.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Two races met when an interop test client finishes publishing: it sends
PUBLISH_DONE and its last data streams, then closes its control stream (an -18
§3.3 protocol violation) and its session. The relay closed the session on the
control stream's FIN at once, cutting streams still arriving, and its session
clean-up ended the publisher's tracks with GOING_AWAY, racing the PUBLISH_DONE
that had just come in, so subscribers saw GOING_AWAY and a reset stream.

Now a closed control stream closes the session once nothing of the peer's
publishing is left (every track it published ended, every data stream of its
read), or after PUBLISH_DONE_STREAM_WAIT. Session clean-up leaves each track to
its own request stream, which ends it with the PUBLISH_DONE status it read, or
GOING_AWAY; and a PUBLISH_DONE waiting on streams stops waiting once the
publisher's session is gone. Tested at -18 with a session written from -18's
messages, and over -22, -18 and -16 with a close after PUBLISH_DONE.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
-16 §9.1 asks for each new Request ID to be "the next in sequence", but its
SUBSCRIBE_NAMESPACE rides a bidirectional stream of its own while the other
requests ride the control stream, and QUIC does not order one stream against
another. A public relay network's -16 endpoint opened a SUBSCRIBE_NAMESPACE
(ID 1) and sent a PUBLISH_NAMESPACE (ID 3) on the control stream; the 3 came
first and the bridge closed the session. The bridge now admits a new ID of the
peer's parity within the grant and leaves the gap for the one in flight; an ID
seen before still closes the session with INVALID_REQUEST_ID, and one past the
grant with TOO_MANY_REQUESTS. a_raw_16_session sends 4 before 2 and then 4
again.

wai-receipts collects until every object has arrived (by alias, group and
object), not a count of streams: a relay may carry a subgroup on more streams
than its publisher opened, and the count stopped the subscriber before the last
group.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The -18 table gains a requester's FIN on namespace requests, the -16 notes the
Request IDs admitted across streams; the relay README states how it ends a
publisher's tracks and treats a closed control stream; -14 is listed as not
implemented. The conformance workflow runs the -18 and -16 independent
decoders beside -22's. Crate docs and descriptions name the three drafts.
joule-code and wai CHANGELOG entries.

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]>
The runner's test cases and WAI receipts end to end against each relay the
interoperability runner's registry lists, at each draft both speak, over QUIC
and WebTransport; and other implementations' test clients against
joule-moq-relay. Relays and clients are named by what they offer; each result
that is not a pass names its cause.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into joule-moq/multi-draft
Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 6s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 5s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 4m6s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 2m45s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 2m43s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 16s
WAI conformance / does this change touch it (pull_request) Successful in 10s
WAI energy receipts / does this change touch it (pull_request) Successful in 6s
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m31s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m9s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 4m7s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 30s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 10m56s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 19m41s
WAI conformance / v2 vocabulary · manifests validate as stated (pull_request) Successful in 3m57s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 17m7s
WAI conformance / delivery telemetry · CMCD and CMSD against an independent library (pull_request) Successful in 1m2s
WAI conformance / cross-arch byte-equality · linux-arm64 (pull_request) Successful in 1m25s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 5m16s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 34m40s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 17m48s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 1h5m9s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 7m54s
standards workspaces build + test / macOS energy backends (pull_request) Failing after 2m54s
WAI conformance / cargo-class corpora · macos (pull_request) Successful in 13m24s
WAI conformance / cross-arch byte-equality · macos (pull_request) Successful in 20s
WAI conformance / codec wrappers · fuzz smoke, text decode limits, C entry points, provenance, the semantic path (pull_request) Successful in 48s
WAI energy receipts / energy receipts · macos (pull_request) Successful in 5m40s
f52a0c70e8
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Merge origin/main into joule-moq/multi-draft
Some checks failed
wai-pack / wai-pack · linux-arm64 (pull_request) Has been skipped
wai-pack / wai-pack · macos (pull_request) Has been skipped
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been skipped
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been skipped
wai-pack / wai-pack · dependency licences (pull_request) Has been skipped
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m36s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 5s
wai-quantum studio and desktop / studio · browser (pull_request) Has been skipped
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been skipped
wai-web wasm32 / does this change touch it (pull_request) Successful in 8s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been skipped
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m20s
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 5m21s
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 27s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m38s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 9m14s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m56s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 49s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m40s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 11m10s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 31m12s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 20m1s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 6m18s
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (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
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled
1fa6a0ba8a
wai/CHANGELOG.md: both entries kept, this branch's above main's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
dcharlot closed this pull request 2026-10-22 12:02:44 -04:00
Some checks failed
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 6s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-web reach builds / wai-web reach · does this change touch it (pull_request) Successful in 10s
wai-web reach builds / wai-web reach builds · the package of record, every output as recorded (pull_request) Has been skipped
wai-web reach builds / wai-web reach builds · this tree's wai-web, every rung on its engine line (pull_request) Has been skipped
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 3m10s
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 2m44s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 2m7s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 6s
wai cross-architecture byte-equality / does this change touch it (pull_request) Successful in 15s
wai cross-architecture byte-equality / linux-aarch64 (pull_request) Has been skipped
wai cross-architecture byte-equality / linux-x86_64 (pull_request) Has been skipped
wai cross-architecture byte-equality / macos-arm64 (reference) (pull_request) Has been skipped
WAI conformance / does this change touch it (pull_request) Successful in 18s
WAI energy receipts / does this change touch it (pull_request) Successful in 15s
WAI HTTP delivery / WAI HTTP · does this change touch it (pull_request) Successful in 11s
WAI HTTP delivery / HTTP corpus · falsify mutations 0/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify mutations 1/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify mutations 2/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 0/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 1/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · falsify patches 2/3 (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · every patch and mutation in exactly one shard (pull_request) Has been skipped
WAI HTTP delivery / HTTP corpus · verified both ways, and the differential (pull_request) Has been skipped
WAI HTTP delivery / cache rules · controls and labelled caches (pull_request) Has been skipped
wai-ops-gpu integer operators on GPUs / does this change touch it (pull_request) Successful in 6s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (pull_request) Has been skipped
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (pull_request) Has been skipped
wai-pack / wai-pack · does this change touch it (pull_request) Successful in 6s
wai-pack / wai-pack · linux (pull_request) Has been skipped
wai-pack / wai-pack · linux-arm64 (pull_request) Has been skipped
wai-pack / wai-pack · macos (pull_request) Has been skipped
wai-pack / wai-pack · independent verifier (Python) (pull_request) Has been skipped
wai-pack / wai-pack · the floor through a media prober (pull_request) Has been skipped
wai-pack / wai-pack · dependency licences (pull_request) Has been skipped
repository checks / lockfiles, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 1m36s
wai-quantum studio and desktop / does this change touch it (pull_request) Successful in 5s
wai-quantum studio and desktop / studio · browser (pull_request) Has been skipped
wai-quantum studio and desktop / desktop app · macos (pull_request) Has been skipped
wai-web wasm32 / does this change touch it (pull_request) Successful in 8s
wai-web wasm32 / wai-web wasm32 build · corpus, menu and writers (pull_request) Has been skipped
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 1m20s
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 5m21s
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 27s
WAI conformance / C2PA manifest emission · signed, read back, validated (pull_request) Successful in 1m38s
WAI energy receipts / energy receipts · linux-arm64 (pull_request) Successful in 9m14s
WAI conformance / quantum core and corpus under wasm32-wasip2 (wasmtime) (pull_request) Successful in 2m56s
WAI conformance / cross-arch byte-equality · linux (pull_request) Successful in 49s
WAI conformance / cargo-class corpora · linux-arm64 (pull_request) Successful in 19m40s
WAI energy receipts / energy receipts · linux (pull_request) Successful in 11m10s
WAI conformance / independent verifiers · claims, JWP, key discovery and integer payloads (Python) (pull_request) Successful in 31m12s
WAI conformance / cargo-class corpora · linux (pull_request) Successful in 20m1s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 6m18s
WAI energy receipts / energy receipts · macos (pull_request) Has been cancelled
WAI conformance / cargo-class corpora · macos (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
standards workspaces build + test / macOS energy backends (pull_request) Has been cancelled

Pull request closed

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!401
No description provided.