diff --git a/quest/m0/README.md b/quest/m0/README.md index 8edcdcd9e04..f5d620e3d99 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -59,7 +59,7 @@ remain and no release waits on them. - [Self-hosted CI](/quest/m0/self-hosted-ci.md) - same-repo Check and Test run on a self-hosted NixOS runner with a main-written local cache, behind a `CI_RUNNER` kill switch - [CI host](/quest/m0/ci-host.md) - the maintainer brings up the spare desktop as the `moq-ci` and `moq-gpu` runner host -- [Draft-22 media on 0.17](/quest/m0/release-22/README.md) - a 0.17.x with the LOCATION_FILTER and FIRST_OBJECT fixes and moq-noq 1.3.5, before Seattle +- [Draft-22 media on 0.17](/quest/m0/release-22/README.md) - a 0.17.x with the LOCATION_FILTER and FIRST_OBJECT fixes and moq-noq 1.3.5, before Seattle, then draft-20+ FETCH - [TS duration fidelity](/quest/m0/ts-duration-fidelity.md) - Interop's TS compliance captures the whole round-tripped stream again, fixing a regression that turns Interop red on main - [web-transport releases the qmux fixes](/quest/m0/qmux-credit-upstream.md) - waiting on moq-dev/web-transport#412 and #413 to merge and ship, which qmux credit bumps to - [qmux credit](/quest/m0/qmux-credit.md) - qmux returns connection credit for dropped and stopped streams and delivers its close frame, on both lines diff --git a/quest/m0/release-22/README.md b/quest/m0/release-22/README.md index bafb0aa0214..e6cf8827ce5 100644 --- a/quest/m0/release-22/README.md +++ b/quest/m0/release-22/README.md @@ -27,9 +27,12 @@ Decided 2026-10-08: - Offer the reporter the `release` head for their rig once the children land, but don't gate the cut on their result. -The backports have landed, including moq-noq 1.3.5 in #5170 (refreshed in the -2026-10-10 audit). This README's remaining work: bump `@moq/net` on -`release` (`/bump`), merge the release-plz PR it regenerates for the -backports (0.17.3; 0.17.2 already shipped, and #4944 predates the -backports), and send the reporter the branch. +The backports shipped in moq-relay 0.17.3 (#5119) and `@moq/net` 0.4.3 +(refreshed 2026-10-12). Draft-22 interop on 2026-10-12 then found FETCH still +refused on draft-20+ sessions. This README's remaining work: once the child +lands, cut the next 0.17 patch and `@moq/net` with it (`/bump`, then the +release-plz PR), and tell the testers. +## Required + +- [Draft-20+ FETCH](/quest/m0/release-22/fetch.md) - backport #4971 so a draft-22 FETCH is served, not refused diff --git a/quest/m0/release-22/fetch.md b/quest/m0/release-22/fetch.md new file mode 100644 index 00000000000..1a39ceae487 --- /dev/null +++ b/quest/m0/release-22/fetch.md @@ -0,0 +1,17 @@ +# [S] Draft-20+ FETCH on the 0.17 release line + +## Goal + +A 0.17.x moq-relay serves single-group FETCH on draft-20 and later sessions, +instead of refusing with "FETCH not supported on draft-20" on draft-22. + +## Plan + +Reported from draft-22 interop on 2026-10-12 against moq-relay 0.17.3. +`main` already serves it ([#4971](https://github.com/moq-dev/moq/pull/4971), +"serve draft-20 FETCH within one group"); `release` still refuses on +`Filter::is_draft20(version)`. + +Decided 2026-10-12: backport #4971 onto `release` as its own PR +(CONTRIBUTING), including its tests, then cut the next 0.17 patch with the +release-plz PR. Mirror the `@moq/net` side if #4971 touched `js/net`. diff --git a/quest/m1/README.md b/quest/m1/README.md index 40e50da469f..ec0d9bda73b 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -88,6 +88,8 @@ folded into Dropped sources. - [JSON window timestamps](/quest/m1/json-window-timestamps.md) - Rust and JS window consumers return each event's frame timestamp like the snapshot and stream consumers - [JavaScript FETCH](/quest/m1/js-fetch.md) - browser publishers answer IETF FETCH through the JS ranges request surface - [IETF fill timeout](/quest/m1/ietf-fill-timeout.md) - native FILL_TIMEOUT bounds upstream waiting on drafts 18-22, alongside local content-age enforcement +- [IETF group ingest refuses loudly](/quest/m1/ietf-ingest-refusal.md) - a subgroup other than 0 or a datagram past object 0 aborts the track, and a peer's malformed group is refused rather than dropped at debug +- [Draft 17-18 withdraw by reset](/quest/m1/ietf-withdraw-reset.md) - withdrawing a PUBLISH_NAMESPACE on drafts 17 and 18 resets the request instead of FIN-ing it - [Relay session limits](/quest/m1/relay-session-limits.md) - moq-relay sets per-session request limits, tighter for clients than peers, and the bindings name a refused request - [Churn with held subscriptions](/quest/m1/session-churn-held.md) - opening and closing a request costs the same with 1 or 1,024 held subscriptions - [Catalog track identity](/quest/m1/catalog-tracks.md) - a track's codec and description never change for its name; resolution changes in band below ceilings fixed at creation, and anything else mints a new rendition or epoch diff --git a/quest/m1/ietf-ingest-refusal.md b/quest/m1/ietf-ingest-refusal.md new file mode 100644 index 00000000000..b7e2453525d --- /dev/null +++ b/quest/m1/ietf-ingest-refusal.md @@ -0,0 +1,52 @@ +# [S] IETF group ingest refuses loudly + +## Goal + +A moq-transport publisher whose groups moq-net cannot represent gets a loud +refusal instead of a silently truncated track. A subgroup ID other than 0 +or a datagram past object 0 aborts the track with Unsupported, logged at +warn. A peer's malformed group is refused with STOP_SENDING instead of being +dropped at debug while the handler returns `Ok(())`. + +## Plan + +Subgroups and object ID gaps stay unsupported on purpose +([#4926](https://github.com/moq-dev/moq/issues/4926), declined). Interop on +2026-10-12 (a LOC publisher at draft-18, one GOP per group and one object per +frame, and a draft-22 test suite) found that the refusal is quiet. In +`rs/moq-net/src/ietf/subscriber.rs`: + +- `recv_group` refuses a subgroup other than 0 with Unsupported, which + resets only that stream, so subgroup 0 is forwarded as if whole. +- `recv_datagram` drops a datagram past object 0 at debug and returns + `Ok(())`. Object 0 is forwarded with `end_of_group` set, so viewers get + truncated groups and no error. +- The catch-all `Err(err)` arm after `GroupIngest` logs at debug and returns + `Ok(())`. Gaps no longer reach it (#5233 returns Unsupported), but a + malformed object extension block (`Decode`), a FIN before the declared + payload ends (`WrongSize`), a second stream for a live group + (`Duplicate`), and an oversized frame or group (`FrameTooLarge`, + `GroupTooLarge`) still do. + +Decided 2026-10-12: + +- **Abort the track** on the first subgroup other than 0 or datagram past + object 0, with Unsupported at warn. The subscriber sees an error, not + partial media. +- **Narrow the catch-all.** Route the peer's faults (`Decode`, `WrongSize`, + `Duplicate`, `FrameTooLarge`, `GroupTooLarge`) to the refusing arm so the dispatcher sends STOP_SENDING. + Peer resets, transport errors and Cancel stay at debug. +- One PR for both, since they share the error routing. +- **Docs.** Record in the deliberate deviations of + `doc/concept/standard.md` that per-subgroup and per-datagram publisher + priority is rewritten to the track's (the model's `group::Info` has no + priority), beside the existing dropped-properties entry, and that groups + past subgroup 0 or object 0 abort the track. +- One regression test per case through the IETF dispatch loop, each failing + without its fix. + +## Related + +- [#5371](https://github.com/moq-dev/moq/issues/5371) - closed: its gap case was fixed by #5233, and it pointed at the catch-all this narrows + +- [Malformed moq-transport input](/quest/m2/ietf-malformed-close.md) - session-level malformed control input diff --git a/quest/m1/ietf-withdraw-reset.md b/quest/m1/ietf-withdraw-reset.md new file mode 100644 index 00000000000..ab09866b962 --- /dev/null +++ b/quest/m1/ietf-withdraw-reset.md @@ -0,0 +1,27 @@ +# [XS] Draft 17-18 withdraw by reset + +## Goal + +On drafts 17 and 18, moq-net withdraws a PUBLISH_NAMESPACE by resetting the +request stream and sending STOP_SENDING, as drafts 19 and later already do, +so quiche-moq no longer closes the session on withdrawal. + +## Plan + +Reported from interop on 2026-10-12. The withdraw path in +`rs/moq-net/src/ietf/publisher.rs` writes PUBLISH_NAMESPACE_DONE then FIN on +drafts 14-16, a plain FIN on 17-18, and aborts both directions on 19+. +Draft-17 §3.3.1 and draft-18 §3.3.2 both say a request is cancelled by +resetting or STOP_SENDING, and that a PUBLISH_NAMESPACE is withdrawn by +cancelling it. FIN is allowed only for SUBSCRIBE_NAMESPACE and +SUBSCRIBE_TRACKS. + +Decided 2026-10-12: + +- Abort both directions on every version after 16. Drafts 14-16 keep + PUBLISH_NAMESPACE_DONE. +- Keep `fin_cancels` accepting a peer FIN as cancellation on 14-18: lenient + on receive. +- Extend `namespace_requester_fin_is_version_gated` (or a sibling) to assert + a reset on 17 and 18. +- Mirror in `js/net/src/ietf` if it has the same gap. diff --git a/quest/m1/stats/rust.md b/quest/m1/stats/rust.md index 3ac342e2e3f..450e7054be2 100644 --- a/quest/m1/stats/rust.md +++ b/quest/m1/stats/rust.md @@ -26,7 +26,8 @@ `rs/moq-audio/src/playback` is already counted privately. - `moq-cli` folds the counters into `hang::stats::Snapshot` on the stats interval and writes it through `moq_json::snapshot`, with the `.z` sibling. - A TS import flattens `ts::stats::Snapshot` in as `mpegts`. `transport` comes from + A TS import flattens `ts::stats::Snapshot` in as `mpegts`, and a TS + export flattens its egress counters into `.echo` the same way. `transport` comes from the connection's `ConnectionStats`. - Feedback: after reading a catalog with an `echo` section, the player resolves the echo path against the broadcast, appends `.echo`, and diff --git a/quest/m1/stats/schema.md b/quest/m1/stats/schema.md index 1ecb593c1e6..b8c5f2b81a4 100644 --- a/quest/m1/stats/schema.md +++ b/quest/m1/stats/schema.md @@ -24,7 +24,10 @@ and the hang draft specs both. Nothing produces them yet. way `Catalog` takes `ts::Ext`. `Track` holds sent frames, sent bytes, keyframes, skipped frames, and the target bitrate as a gauge. - `rs/hang/src/echo.rs`: - `Snapshot { transport, renditions: BTreeMap }`. `Track` + `Snapshot { transport, renditions: BTreeMap, #[serde(flatten)] ext: E }`, + the same flatten as `stats::Snapshot`, so a TS export folds its + `mpegts` egress counters in (decided 2026-10-12, from + [#5360](https://github.com/moq-dev/moq/issues/5360)). `Track` holds: - received frames and bytes, decoded, late, and decode errors; - stalls, stalled duration, and underruns; @@ -51,10 +54,14 @@ and the hang draft specs both. Nothing produces them yet. schemas, and the `.echo` convention: one broadcast per soliciting catalog, under the catalog's echo path, announced after the viewer reads the catalog. Validate with `just drafts check`. -- Open: the MSF catalog conversion in `rs/moq-mux` copies only the media - sections and `ext`, so `stats` and `echo` drop silently under - `--catalog-format msf`. Candidates: carry them through MSF with a - round-trip test, or refuse `--stats` and `--echo` with MSF. +- **MSF carries neither section yet** (decided 2026-10-12). The MSF + conversion in `rs/moq-mux` copies only the media sections and `ext`. A + viewer given `--echo` with `--catalog-format msf` refuses, since it would + never see an `echo` section to answer. An import has no catalog-format + flag and always emits the MSF catalog beside the hang one, so that catalog + omits `stats` and `echo` the way it omits `json` and `binary`; hang + readers still get them. Carrying them through MSF `publishTracks` is + [MSF feedback tracks](/quest/m3/msf-publish-tracks.md). - Docs: `doc/concept/hang.md`, and a media section in `doc/concept/stats.md`. - Tests: - fixtures round-trip in both languages; diff --git a/quest/m1/tstd/README.md b/quest/m1/tstd/README.md index aeeaf2754e3..042aee60ba7 100644 --- a/quest/m1/tstd/README.md +++ b/quest/m1/tstd/README.md @@ -37,6 +37,8 @@ nightly. - [Send-ahead within the delay](/quest/m1/tstd/send-ahead.md) - total lag is `--delay`, send-ahead included, with a 1 s default - [PSI runs](/quest/m1/tstd/psi-runs.md) - the PAT and PMT are deduplicated and paced so the system buffers never overflow +- [Video at its full leak](/quest/m1/tstd/video-leak.md) - a leak-bound video PID is admitted at its full Rbx, not about 3 % below it +- [Mux overhead](/quest/m1/tstd/mux-overhead.md) - the PCR rides in the video's adaptation field and audio PES carry several frames, so a full CBR source re-muxes at its own rate ## Related diff --git a/quest/m1/tstd/mux-overhead.md b/quest/m1/tstd/mux-overhead.md new file mode 100644 index 00000000000..f1b604c7197 --- /dev/null +++ b/quest/m1/tstd/mux-overhead.md @@ -0,0 +1,41 @@ +# [M] TS export fits a full source at its own rate + +## Goal + +`moq export ts` spends no more packets than the source on clocks and audio +framing, so a CBR source that fills its multiplex with no null packets +re-muxes at its own `--mux-rate` instead of falling behind. + +## Plan + +Reported in [#5362](https://github.com/moq-dev/moq/issues/5362) point 3 and +verified against `rs/moq-mux/src/container/ts/export.rs`: with points 1 and 2 +fixed, the export of a 25 Mb/s broadcast feed averages 25.017 Mb/s of +non-null packets. Against the source it spends about 40 packets/s on +separate PCR packets (the source carries its PCR in video packets) and about +60 on one audio frame per PES (the source packs about twelve MP2 and nine +AC-3 frames per PES). + +Decided 2026-10-12: + +- **PCR in the PCR PID's adaptation field.** When the clock is due in a slot + that sends a PCR-PID packet, carry the PCR in that packet's adaptation + field, sampled at its byte position as today. Fall back to the standalone + `pcr_packet` only when the slot has none. The 25 ms grid stays; the + comment at `lay` explains why the PCR is not sampled from the DTS. +- **Several audio frames per PES.** Pack consecutive frames of an audio PID + into one PES while the PES fits the PID's B, timed by the first frame's + PTS. This applies to every audio stream the export frames (AAC, MP2, AC-3, + E-AC-3), not only DVB AC-3. +- Land after [#5363](https://github.com/moq-dev/moq/pull/5363), which reworks + the DVB AC-3 framing this packs. +- Test: a generated CBR source with no nulls, at the source's own + `--mux-rate`, exports for the whole clip and passes the strict T-STD check. + +## Closes + +- [#5362](https://github.com/moq-dev/moq/issues/5362) - a full-rate CBR contribution feed exports at its own rate + +## Related + +- [TS export admits video at its full leak](/quest/m1/tstd/video-leak.md) - #5362 point 2 diff --git a/quest/m1/tstd/video-leak.md b/quest/m1/tstd/video-leak.md new file mode 100644 index 00000000000..9e52be30df7 --- /dev/null +++ b/quest/m1/tstd/video-leak.md @@ -0,0 +1,31 @@ +# [S] TS export admits video at its full leak + +## Goal + +`moq export ts` admits a leak-bound video PID at its full Rbx rate, so a +source that fills its level's leak (no HRD, 1080i25 High@4.0 at Rbx 24 Mb/s) +exports without falling behind and missing a decode deadline. + +## Plan + +Reported in [#5362](https://github.com/moq-dev/moq/issues/5362) point 2 and +verified against `rs/moq-mux/src/container/ts/`: `Buffer::per_slot` +(`schedule.rs`) floors the rate to whole 188-byte packets per 25 ms slot and +subtracts one packet of slack, and `admit` charges each slot's clock packet to +the PCR PID, which is the video. Together that admits about 2.9 % below what +Rbx carries in payload bytes. The slack's only rationale is a comment about +bunching at slot edges; no commit or quest calls it a deliberate margin. + +Decided 2026-10-12: + +- **Exact accounting.** Cap a leak-bound PID at the lesser of Rx in packets + and Rbx in payload bytes, since Rbx governs payload into EB while TB drains + at Rx. Carry fractional packets from slot to slot and drop the one-packet + slack. A standalone PCR packet still enters the video's transport buffer, + so it counts against the Rx packet budget but not the Rbx payload budget. +- Test: a generated source whose video fills Rbx on average exports for the + whole clip and passes the strict T-STD check that `just test ts --hrd` runs. + +## Related + +- [Lower TS mux overhead](/quest/m1/tstd/mux-overhead.md) - #5362 point 3, which closes the issue diff --git a/quest/m2/ts-health-stats.md b/quest/m2/ts-health-stats.md index 9cff6c10dcd..238754b71e4 100644 --- a/quest/m2/ts-health-stats.md +++ b/quest/m2/ts-health-stats.md @@ -2,7 +2,7 @@ ## Goal -`moq import ts --stats` and `moq export ts --stats` publish the `ts::stats` +`moq import ts --stats` and `moq export ts --echo` publish the `ts::stats` counters so a dashboard reads the TR 101 290 counters and per-PID liveness beside the media and delivery counters, with no new transport. No health roll-up in-tree: green, amber or red per priority is the consumer's reading of @@ -17,12 +17,15 @@ Decided while planning [#1838](https://github.com/moq-dev/moq/issues/1838): import flattens `ts::stats::Snapshot` into the publisher's stats snapshot under the catalog's `mpegts` key, the counters stay owned by moq-mux beside `ts::Ext`, and `hang` stays TS-free. -- Open: an export is a viewer, which has no stats track and reports only - through a soliciting catalog's `.echo` feedback, so where `moq export ts - --stats` publishes its egress rows is unsettled. Candidates: extend the - solicited `.echo` snapshot with a flattened container section, give viewers - their own stats surface, or narrow this quest to import. Leaning toward - narrowing, since the other two widen the settled stats design. +- **The export reports through `.echo`** (decided 2026-10-12, from + [#5360](https://github.com/moq-dev/moq/issues/5360)). An export is a viewer, + so `moq export ts --echo ` folds `ts::stats::Snapshot` into its + solicited `.echo` snapshot under `mpegts`, through the `E` flatten the + [media stats schema](/quest/m1/stats/schema.md) gives `echo::Snapshot`. + The egress is where an operator otherwise cannot see per-PID outages: a + 57 s video hole passed every TR 101 290 P1 check in the reporter's lab, + and only per-PID access-unit counts caught it. There is no export + `--stats` flag. - **Counters sum, gauges do not.** Every check is a cumulative counter, which keeps `.z` deltas small and lets an aggregate sum; the PID's `quiet` is a gauge and merges newest-wins. @@ -32,7 +35,8 @@ Decided while planning [#1838](https://github.com/moq-dev/moq/issues/1838): monitoring point (ingest grades the feed, egress grades our muxer, and neither is the groomed wire), the value domain of the PCR checks, and that the per-PID rows cover `PID_error`; `doc/bin/cli.md` the flags. -- Test: publish a TS clip with `--stats`, read it back, and assert every ETSI +- Test: publish a TS clip with `--stats` and export it with `--echo` to a + soliciting catalog, read both back, and assert every ETSI counter reads zero on a clean clip while every elementary stream's access-unit count advances. diff --git a/quest/m3/README.md b/quest/m3/README.md index 862d9368bab..93d6406bfe1 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -59,6 +59,7 @@ m4's upstream waits in here and moved m2 work with no consumer. - [fMP4 emsg](/quest/m3/emsg.md) - settles the shared framing and missing-data semantics before this section adopts them - [ID3 catalog section](/quest/m3/id3.md) - timed ID3 as a first-class container-neutral catalog section - [FLV script tags](/quest/m3/flv-script.md) - onMetaData and AMF data messages survive RTMP and FLV import +- [MSF feedback tracks](/quest/m3/msf-publish-tracks.md) - `--stats` and `--echo` work under `--catalog-format msf` through `publishTracks`, once a consumer asks - [MSFTS convergence](/quest/m3/msfts-convergence.md) - the demultiplexed TS lane converges on MSFTS where they differ, once an MSFTS implementer engages - [#2279](/quest/m3/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md) - hang: SCTE-35 cues arrive immediately on an independent metadata track, optionally associated with a rendition - [CEA-608/708](/quest/m3/captions-cea.md) - captions carried inside video SEI become a real text rendition at import diff --git a/quest/m3/msf-publish-tracks.md b/quest/m3/msf-publish-tracks.md new file mode 100644 index 00000000000..c43ed4a12fd --- /dev/null +++ b/quest/m3/msf-publish-tracks.md @@ -0,0 +1,24 @@ +# [M] MSF feedback tracks + +## Goal + +`--stats` and `--echo` work under `--catalog-format msf`: a soliciting +catalog's `echo` maps onto an MSF `publishTracks` entry (draft-ietf-moq-msf +§5.1.5), and a viewer publishes the hang `.echo` snapshot there. Start once a +consumer asks. + +## Plan + +Raised in [#5360](https://github.com/moq-dev/moq/issues/5360). Until then the +[media stats schema](/quest/m1/stats/schema.md) leaves both sections out of +the MSF catalog and refuses a viewer's `--echo` under `--catalog-format msf` +(decided 2026-10-12). + +Open: how the entry names the hang payload, since MSF fixes its packaging +values in the draft; and whether to add a CMCD v2 Event Mode encoding +(draft-wilaw-moq-cmcd-event-timeline) of the overlapping fields (stalls, +dropped frames, latency, throughput, errors) for consumers that ingest CMCD. + +## Required + +- [Media stats schema](/quest/m1/stats/schema.md) - the `stats` and `echo` sections this maps