From d59f166dc07371ba3173a36150a44e7efd223719 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 30 Sep 2026 07:04:37 -0700 Subject: [PATCH 1/3] quest: audit moves, deletes, and merges (structural) Co-Authored-By: Claude Opus 5.5 --- quest/m0/README.md | 9 +- quest/m0/audio-jitter-target/README.md | 3 +- quest/m0/audio-jitter-target/watch.md | 6 +- quest/m0/ietf-fin-not-cancel.md | 1 - quest/m0/ietf-legal-input.md | 5 - quest/m0/ietf-subgroup-refusal.md | 22 ++++ quest/{m1 => m0}/ietf-uni-stream-types.md | 0 quest/m0/noq-reassembly-cap.md | 1 - quest/m0/qmux-reset-race.md | 4 - quest/m0/remove-gossip.md | 4 - quest/m0/shared-fronts.md | 1 - quest/m0/wildcard/README.md | 8 +- ...s-dropping-one-split-server-resizes-the.md | 35 ------ quest/m1/README.md | 41 +------ quest/m1/archive/README.md | 2 - quest/m1/audio-codecs/README.md | 1 - quest/m1/auth/README.md | 4 +- quest/m1/auth/lite.md | 5 - quest/m1/auth/peer-grant.md | 4 +- quest/m1/auth/relay-refresh.md | 1 - quest/m1/auth/request-token.md | 1 - quest/{m0/plan-av-clock.md => m1/av-clock.md} | 0 quest/m1/bbr-ack-cleanup.md | 1 - quest/m1/bench-ci.md | 1 - quest/m1/bindings-graceful-close.md | 15 --- quest/m1/cache-expiry-growth.md | 4 - quest/m1/cache-wall-eviction.md | 1 - quest/m1/capture-control.md | 50 --------- quest/m1/cli-serve.md | 4 - quest/m1/close-codes.md | 42 ------- .../README.md} | 2 - quest/m1/color-model.md | 5 - quest/m1/cpp/README.md | 3 - quest/m1/cpp/generator.md | 2 +- quest/m1/cross-relay-bursts.md | 4 - quest/m1/data-track-clock.md | 4 - quest/m1/drain/README.md | 4 - quest/m1/effect-cancel.md | 12 -- quest/m1/ffi-shape/README.md | 4 - quest/m1/ffi-shape/codec.md | 2 +- quest/m1/frame-slot-charge.md | 4 - quest/m1/gpu-ci.md | 1 - quest/m1/hls-discontinuity-sequence.md | 37 ------ quest/m1/hop-aligned-import.md | 1 - quest/{m2 => m1}/ietf-drain-before-close.md | 4 - quest/m1/js-closed-track-leak.md | 18 --- quest/m1/js-fetch.md | 4 - quest/{m2 => m1}/mobile-ownership.md | 0 quest/m1/moxygen/fetch.md | 4 - quest/m1/obs-moq-video/README.md | 6 - quest/m1/obs-moq-video/linux-bundle.md | 4 - quest/m1/obs-moq-video/preset-parity.md | 4 +- quest/m1/path-patterns.md | 105 ------------------ ...-moq-uring-add-opt-in-napi-busy-polling.md | 30 ----- ...-reusable-io-uring-enter-wait-arguments.md | 35 ------ quest/m1/perf/README.md | 19 ---- quest/m1/perf/coding-decode.md | 31 ------ quest/m1/perf/egress-keepalive.md | 35 ------ quest/m1/perf/egress-requeue.md | 31 ------ quest/m1/perf/group-cost.md | 4 - quest/m1/perf/lock-wait.md | 5 - quest/m1/perf/priority-set-track-wakes.md | 42 ------- quest/m1/perf/uring-one-enter.md | 2 - quest/m1/perf/uring-quiescence.md | 5 - quest/m1/performance-profiles.md | 1 - quest/m1/plan-watch-worker.md | 1 - quest/{m2 => m1}/play-drain-tail.md | 0 quest/m1/qos/README.md | 3 - quest/m1/qos/starvation.md | 4 +- quest/m1/quic/README.md | 23 ---- quest/m1/quic/release.md | 32 ------ quest/m1/quic/reliable-reset.md | 2 - quest/m1/quic/scheduler.md | 4 +- quest/m1/quic/upstream.md | 15 +-- quest/m1/raw-stream-codes.md | 9 -- quest/m1/relay-iroh-opt-in.md | 5 - quest/m1/relay-memory.md | 41 ------- quest/m1/remove-live.md | 49 -------- quest/m1/rs2ts/README.md | 1 - quest/m1/rs2ts/sans-io/README.md | 1 - quest/m1/rs2ts/sans-io/async-feature.md | 2 +- quest/{m2 => m1}/serve-hls-renditions.md | 0 quest/m1/session-close.md | 34 ------ quest/m1/session-death.md | 4 - quest/m1/stats-producer-bench.md | 33 ------ quest/m1/stats/README.md | 4 +- quest/m1/track-demand.md | 22 ---- quest/m1/ts-import-shared-shift.md | 4 - quest/m1/unknown-session-logs.md | 46 -------- quest/m1/uring-ietf.md | 5 - quest/m1/video-surface.md | 36 ------ quest/m1/watch-audio-time-stretch.md | 3 +- quest/m1/watch-refusal.md | 38 ------- quest/m1/watch-worker.md | 1 - quest/m1/wt-close-upstream.md | 5 - ...te-35-ad-cue-signaling-carried-opaquely.md | 10 +- ...ipewire-dma-bufs-safely-into-the-vulkan.md | 1 - ...-the-webtransport-stream-header-at-open.md | 4 - ...tch-completion-wakeups-with-min-timeout.md | 0 ...fixed-file-slots-for-worker-udp-sockets.md | 0 quest/m2/README.md | 88 +++++++++------ quest/{m3 => m2}/announce-shapes.md | 6 +- quest/m2/archive-backward-timestamps.md | 16 --- .../browser.md => m2/archive-browser.md} | 0 .../archive-paced-replay.md} | 0 quest/m2/audio-decode-mediafoundation.md | 5 - quest/m2/audio-encode-mediafoundation.md | 4 - quest/m2/audio-loss-recovery.md | 28 ----- quest/m2/av1-metadata.md | 10 -- quest/{m1 => m2}/bench-coverage.md | 0 quest/m2/browser-media-qa-engines.md | 4 - quest/{m1/perf => m2}/cache-shard.md | 0 quest/{m1 => m2}/captions-cea.md | 6 - quest/m2/capture-ergonomics.md | 27 ----- quest/m2/capture-frame-buffers.md | 30 ----- quest/m2/carrier-voice/README.md | 56 ---------- quest/m2/carrier-voice/proof.md | 35 ------ quest/m2/carrier-voice/protocol.md | 44 -------- quest/m2/carrier-voice/sip-originate.md | 32 ------ quest/m2/carrier-voice/verdict.md | 44 -------- quest/{m1 => m2}/closure-counters.md | 0 quest/m2/cpp-vcpkg.md | 4 - quest/{m1 => m2}/emsg.md | 3 - quest/m2/{flate/README.md => flate.md} | 4 - quest/m2/flate/bindings.md | 32 ------ quest/{m1 => m2}/flv-script.md | 8 +- quest/{m1 => m2}/id3.md | 11 +- quest/m2/intra-refresh/README.md | 4 - quest/m2/js-discontinuity.md | 22 ---- quest/{m1 => m2}/ladder/README.md | 4 +- quest/{m1 => m2}/ladder/controller.md | 2 +- quest/{m1 => m2}/ladder/fetch.md | 2 +- quest/m2/livekit-webrtc-bridge.md | 27 ----- quest/m2/mobile-capture-android.md | 4 - quest/m2/mobile-capture-ios.md | 4 - quest/m2/mobile-completion.md | 1 - quest/{m1 => m2}/moq-install-url.md | 5 - quest/{m1 => m2}/moq-installer.md | 4 - quest/{m1 => m2}/moq-relay-subcommand.md | 0 quest/{m4 => m2}/msfts-convergence.md | 0 .../obs-decode-linux.md} | 4 - .../obs-decode-windows.md} | 0 .../macos.md => m2/obs-macos.md} | 0 .../windows.md => m2/obs-windows.md} | 0 quest/{m1 => m2}/one-port/README.md | 11 +- quest/{m1 => m2}/one-port/srt-demux.md | 4 +- quest/{m1 => m2}/one-port/tcp-demux.md | 2 +- quest/{m1 => m2}/one-port/udp-demux.md | 6 +- quest/{m1 => m2}/p2p/README.md | 31 +++--- quest/{m1 => m2}/p2p/cli.md | 6 +- quest/{m1 => m2}/p2p/cost-scopes.md | 10 +- quest/{m1 => m2}/p2p/harness.md | 6 +- quest/{m1 => m2}/p2p/signal.md | 14 +-- quest/{m1 => m2}/p2p/transit.md | 6 +- quest/{m1 => m2}/p2p/transport.md | 6 +- quest/{m1 => m2}/p2p/unordered.md | 9 +- quest/{m1 => m2}/p2p/watch.md | 12 +- quest/{m1 => m2}/p2p/webrtc.md | 7 +- quest/m2/pipewire-camera-planes.md | 1 - quest/{m1 => m2}/processor/README.md | 11 +- quest/{m1 => m2}/processor/advertise-auth.md | 2 +- quest/{m1 => m2}/processor/grant-lease.md | 0 quest/{m1 => m2}/processor/media-contract.md | 0 .../quic/ack-hook.md => m2/quic-ack-hook.md} | 12 -- .../quic-ack-progress.md} | 5 - quest/m2/quic-bbr-google.md | 40 ------- .../quic-bbr-loss-parity.md} | 0 quest/m2/quic-bbr-natural-drain.md | 1 - quest/m2/quic-buffer-pool.md | 27 ----- .../quic/deadline.md => m2/quic-deadline.md} | 4 +- .../ecn-measure.md => m2/quic-ecn-measure.md} | 0 quest/m2/quic-ecn.md | 7 +- ...end-batching.md => quic-egress-profile.md} | 5 - quest/m2/quic-fec.md | 30 ----- quest/m2/quic-kernel-pacing.md | 40 ------- quest/m2/quic-probe.md | 2 - quest/{m1/quic/qmux.md => m2/quic-qmux.md} | 0 quest/m2/redundant-ingest.md | 30 ----- quest/{m1/rs2ts/ietf.md => m2/rs2ts-ietf.md} | 1 - .../ietf.md => m2/rs2ts-sans-io-ietf.md} | 0 quest/m2/runtime-qa-hosts.md | 43 ------- quest/m2/{sei/README.md => sei.md} | 9 -- quest/m2/sei/evidence.md | 32 ------ quest/m2/sei/sei-rust.md | 37 ------ quest/m2/sei/sei-web.md | 29 ----- quest/m2/sei/sei.md | 54 --------- quest/{m1 => m2}/signed-priority.md | 0 quest/m2/sip-stack.md | 34 ------ quest/{m1/qos => m2}/starvation-frames.md | 2 - quest/m2/stats-delta.md | 1 - .../stats-encoder-feedback.md} | 2 - quest/m2/teleop/README.md | 21 +--- quest/m2/teleop/correlation.md | 32 ------ quest/m2/teleop/ros2.md | 38 ------- quest/m2/teleop/v4l2-encode.md | 43 ------- quest/{m1 => m2}/text-schema.md | 0 quest/m2/ts-import-health.md | 2 +- quest/{m1/perf => m2}/uring-open-contract.md | 4 - quest/{m1 => m2}/uring-tcp/README.md | 6 +- quest/{m1 => m2}/uring-tcp/ablation.md | 0 quest/{m1 => m2}/uring-tcp/relay.md | 4 +- quest/{m1 => m2}/uring-tcp/stream.md | 2 +- quest/m2/video-codec-coverage.md | 1 - quest/{m4 => m2}/video-vaapi.md | 4 - quest/m2/x11-capture-shm.md | 4 - ...ipewire-dma-buf-capture-on-kde-hardware.md | 66 ----------- ...use-sendmsg-zc-for-large-udp-gso-trains.md | 0 ...ter-tx-pool-buffers-for-zero-copy-sends.md | 4 - quest/m3/README.md | 35 +++++- quest/{m2 => m3}/af-xdp.md | 6 - quest/{m2 => m3}/cat/README.md | 4 +- quest/{m2 => m3}/cat/present.md | 2 +- quest/{m2 => m3}/cat/verify.md | 0 quest/{m2 => m3}/cpp-conan.md | 0 quest/{m2 => m3}/cs/README.md | 5 +- quest/{m2 => m3}/cs/generator.md | 0 quest/{m2 => m3}/cs/package.md | 2 +- quest/m3/dpdk.md | 28 ----- quest/{m2 => m3}/hidden-exemption.md | 0 .../intra-refresh-bindings.md} | 1 - .../intra-refresh-encode-config.md} | 0 .../intra-refresh-nvenc.md} | 4 - .../intra-refresh-v4l2.md} | 4 - quest/{m2 => m3}/livekit-shim.md | 4 - quest/{m2 => m3}/obs-linux-gpu.md | 1 - quest/m3/pipewire-camera-hardware.md | 23 ---- quest/m3/processor-vision.md | 43 ------- quest/{m2 => m3}/quic-gcc.md | 5 - quest/{m2 => m3}/quic-receive-ts.md | 7 +- quest/{m2 => m3}/routing-cost-domains.md | 4 +- .../teleop-browser-package.md} | 0 .../mavlink.md => m3/teleop-mavlink.md} | 0 .../teleop/proof.md => m3/teleop-proof.md} | 5 - quest/{m2 => m3}/unity.md | 2 +- quest/{m2 => m3}/unreal.md | 0 quest/m3/upstream-forks.md | 62 ----------- quest/m3/video-hardware.md | 5 - quest/{m2 => m3}/watch-data-sync.md | 4 - quest/{m2 => m3}/whep-abr.md | 0 quest/m4/README.md | 3 - quest/m4/vaapi-resize-pool.md | 19 ---- 241 files changed, 221 insertions(+), 2651 deletions(-) create mode 100644 quest/m0/ietf-subgroup-refusal.md rename quest/{m1 => m0}/ietf-uni-stream-types.md (100%) delete mode 100644 quest/m1/2964-quic-workers-dropping-one-split-server-resizes-the.md rename quest/{m0/plan-av-clock.md => m1/av-clock.md} (100%) delete mode 100644 quest/m1/bindings-graceful-close.md delete mode 100644 quest/m1/capture-control.md delete mode 100644 quest/m1/close-codes.md rename quest/m1/{cluster-routing.md => cluster-routing/README.md} (98%) delete mode 100644 quest/m1/effect-cancel.md delete mode 100644 quest/m1/hls-discontinuity-sequence.md rename quest/{m2 => m1}/ietf-drain-before-close.md (84%) delete mode 100644 quest/m1/js-closed-track-leak.md rename quest/{m2 => m1}/mobile-ownership.md (100%) delete mode 100644 quest/m1/path-patterns.md delete mode 100644 quest/m1/perf/3203-moq-uring-add-opt-in-napi-busy-polling.md delete mode 100644 quest/m1/perf/3205-moq-uring-register-reusable-io-uring-enter-wait-arguments.md delete mode 100644 quest/m1/perf/coding-decode.md delete mode 100644 quest/m1/perf/egress-keepalive.md delete mode 100644 quest/m1/perf/egress-requeue.md delete mode 100644 quest/m1/perf/priority-set-track-wakes.md rename quest/{m2 => m1}/play-drain-tail.md (100%) delete mode 100644 quest/m1/quic/release.md delete mode 100644 quest/m1/relay-memory.md delete mode 100644 quest/m1/remove-live.md rename quest/{m2 => m1}/serve-hls-renditions.md (100%) delete mode 100644 quest/m1/session-close.md delete mode 100644 quest/m1/stats-producer-bench.md delete mode 100644 quest/m1/track-demand.md delete mode 100644 quest/m1/unknown-session-logs.md delete mode 100644 quest/m1/video-surface.md delete mode 100644 quest/m1/watch-refusal.md rename quest/{m1 => m2}/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md (88%) rename quest/{m1/perf => m2}/3129-moq-uring-write-the-webtransport-stream-header-at-open.md (94%) rename quest/{m1/perf => m2}/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md (100%) rename quest/{m1/perf => m2}/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md (100%) rename quest/{m3 => m2}/announce-shapes.md (92%) delete mode 100644 quest/m2/archive-backward-timestamps.md rename quest/{m1/archive/browser.md => m2/archive-browser.md} (100%) rename quest/{m1/archive/paced-replay.md => m2/archive-paced-replay.md} (100%) delete mode 100644 quest/m2/audio-loss-recovery.md rename quest/{m1 => m2}/bench-coverage.md (100%) rename quest/{m1/perf => m2}/cache-shard.md (100%) rename quest/{m1 => m2}/captions-cea.md (89%) delete mode 100644 quest/m2/capture-ergonomics.md delete mode 100644 quest/m2/capture-frame-buffers.md delete mode 100644 quest/m2/carrier-voice/README.md delete mode 100644 quest/m2/carrier-voice/proof.md delete mode 100644 quest/m2/carrier-voice/protocol.md delete mode 100644 quest/m2/carrier-voice/sip-originate.md delete mode 100644 quest/m2/carrier-voice/verdict.md rename quest/{m1 => m2}/closure-counters.md (100%) rename quest/{m1 => m2}/emsg.md (95%) rename quest/m2/{flate/README.md => flate.md} (88%) delete mode 100644 quest/m2/flate/bindings.md rename quest/{m1 => m2}/flv-script.md (82%) rename quest/{m1 => m2}/id3.md (85%) delete mode 100644 quest/m2/js-discontinuity.md rename quest/{m1 => m2}/ladder/README.md (96%) rename quest/{m1 => m2}/ladder/controller.md (98%) rename quest/{m1 => m2}/ladder/fetch.md (95%) delete mode 100644 quest/m2/livekit-webrtc-bridge.md rename quest/{m1 => m2}/moq-install-url.md (91%) rename quest/{m1 => m2}/moq-installer.md (94%) rename quest/{m1 => m2}/moq-relay-subcommand.md (100%) rename quest/{m4 => m2}/msfts-convergence.md (100%) rename quest/{m1/obs-moq-video/decode-linux.md => m2/obs-decode-linux.md} (89%) rename quest/{m1/obs-moq-video/decode-windows.md => m2/obs-decode-windows.md} (100%) rename quest/{m1/obs-moq-video/macos.md => m2/obs-macos.md} (100%) rename quest/{m1/obs-moq-video/windows.md => m2/obs-windows.md} (100%) rename quest/{m1 => m2}/one-port/README.md (90%) rename quest/{m1 => m2}/one-port/srt-demux.md (94%) rename quest/{m1 => m2}/one-port/tcp-demux.md (96%) rename quest/{m1 => m2}/one-port/udp-demux.md (94%) rename quest/{m1 => m2}/p2p/README.md (85%) rename quest/{m1 => m2}/p2p/cli.md (89%) rename quest/{m1 => m2}/p2p/cost-scopes.md (89%) rename quest/{m1 => m2}/p2p/harness.md (90%) rename quest/{m1 => m2}/p2p/signal.md (91%) rename quest/{m1 => m2}/p2p/transit.md (88%) rename quest/{m1 => m2}/p2p/transport.md (91%) rename quest/{m1 => m2}/p2p/unordered.md (87%) rename quest/{m1 => m2}/p2p/watch.md (74%) rename quest/{m1 => m2}/p2p/webrtc.md (87%) rename quest/{m1 => m2}/processor/README.md (80%) rename quest/{m1 => m2}/processor/advertise-auth.md (98%) rename quest/{m1 => m2}/processor/grant-lease.md (100%) rename quest/{m1 => m2}/processor/media-contract.md (100%) rename quest/{m1/quic/ack-hook.md => m2/quic-ack-hook.md} (85%) rename quest/{m1/quic/ack-progress.md => m2/quic-ack-progress.md} (94%) delete mode 100644 quest/m2/quic-bbr-google.md rename quest/{m1/quic/bbr-loss-parity.md => m2/quic-bbr-loss-parity.md} (100%) delete mode 100644 quest/m2/quic-buffer-pool.md rename quest/{m1/quic/deadline.md => m2/quic-deadline.md} (93%) rename quest/{m1/quic/ecn-measure.md => m2/quic-ecn-measure.md} (100%) rename quest/m2/{quic-send-batching.md => quic-egress-profile.md} (89%) delete mode 100644 quest/m2/quic-fec.md delete mode 100644 quest/m2/quic-kernel-pacing.md rename quest/{m1/quic/qmux.md => m2/quic-qmux.md} (100%) delete mode 100644 quest/m2/redundant-ingest.md rename quest/{m1/rs2ts/ietf.md => m2/rs2ts-ietf.md} (84%) rename quest/{m1/rs2ts/sans-io/ietf.md => m2/rs2ts-sans-io-ietf.md} (100%) delete mode 100644 quest/m2/runtime-qa-hosts.md rename quest/m2/{sei/README.md => sei.md} (60%) delete mode 100644 quest/m2/sei/evidence.md delete mode 100644 quest/m2/sei/sei-rust.md delete mode 100644 quest/m2/sei/sei-web.md delete mode 100644 quest/m2/sei/sei.md rename quest/{m1 => m2}/signed-priority.md (100%) delete mode 100644 quest/m2/sip-stack.md rename quest/{m1/qos => m2}/starvation-frames.md (96%) rename quest/{m1/stats/encoder-feedback.md => m2/stats-encoder-feedback.md} (97%) delete mode 100644 quest/m2/teleop/correlation.md delete mode 100644 quest/m2/teleop/ros2.md delete mode 100644 quest/m2/teleop/v4l2-encode.md rename quest/{m1 => m2}/text-schema.md (100%) rename quest/{m1/perf => m2}/uring-open-contract.md (85%) rename quest/{m1 => m2}/uring-tcp/README.md (90%) rename quest/{m1 => m2}/uring-tcp/ablation.md (100%) rename quest/{m1 => m2}/uring-tcp/relay.md (92%) rename quest/{m1 => m2}/uring-tcp/stream.md (96%) rename quest/{m4 => m2}/video-vaapi.md (94%) delete mode 100644 quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md rename quest/{m1/perf => m3}/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md (100%) rename quest/{m1/perf => m3}/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md (92%) rename quest/{m2 => m3}/af-xdp.md (82%) rename quest/{m2 => m3}/cat/README.md (96%) rename quest/{m2 => m3}/cat/present.md (97%) rename quest/{m2 => m3}/cat/verify.md (100%) rename quest/{m2 => m3}/cpp-conan.md (100%) rename quest/{m2 => m3}/cs/README.md (83%) rename quest/{m2 => m3}/cs/generator.md (100%) rename quest/{m2 => m3}/cs/package.md (94%) delete mode 100644 quest/m3/dpdk.md rename quest/{m2 => m3}/hidden-exemption.md (100%) rename quest/{m2/intra-refresh/bindings.md => m3/intra-refresh-bindings.md} (90%) rename quest/{m2/intra-refresh/encode-config.md => m3/intra-refresh-encode-config.md} (100%) rename quest/{m2/intra-refresh/nvenc-refresh.md => m3/intra-refresh-nvenc.md} (91%) rename quest/{m2/intra-refresh/v4l2-refresh.md => m3/intra-refresh-v4l2.md} (93%) rename quest/{m2 => m3}/livekit-shim.md (95%) rename quest/{m2 => m3}/obs-linux-gpu.md (92%) delete mode 100644 quest/m3/pipewire-camera-hardware.md delete mode 100644 quest/m3/processor-vision.md rename quest/{m2 => m3}/quic-gcc.md (91%) rename quest/{m2 => m3}/quic-receive-ts.md (85%) rename quest/{m2 => m3}/routing-cost-domains.md (93%) rename quest/{m2/teleop/browser-package.md => m3/teleop-browser-package.md} (100%) rename quest/{m2/teleop/mavlink.md => m3/teleop-mavlink.md} (100%) rename quest/{m2/teleop/proof.md => m3/teleop-proof.md} (86%) rename quest/{m2 => m3}/unity.md (94%) rename quest/{m2 => m3}/unreal.md (100%) delete mode 100644 quest/m3/upstream-forks.md rename quest/{m2 => m3}/watch-data-sync.md (86%) rename quest/{m2 => m3}/whep-abr.md (100%) delete mode 100644 quest/m4/vaapi-resize-pool.md diff --git a/quest/m0/README.md b/quest/m0/README.md index d0507ec550..899dc38ab3 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -36,6 +36,11 @@ Published API or wire breaks still land on dev; each quest's Plan says so. ## Required +- [Legal IETF input](/quest/m0/ietf-legal-input.md) - draft-20+ FETCH, allowed parameters, INCLUDE_PROPERTIES and FORWARD=0 decode and are refused per request, not session-fatal +- [IETF FIN semantics](/quest/m0/ietf-fin-not-cancel.md) - a request stream FIN stops updates without cancelling, and REQUEST_UPDATE on a subscribe is parsed +- [Subgroup refusal](/quest/m0/ietf-subgroup-refusal.md) - a non-zero moq-transport subgroup costs that one stream, never the session +- [IETF stream types](/quest/m0/ietf-uni-stream-types.md) - padding streams are discarded stream-only and an unknown uni type closes the session, per draft-21 +- [Request caps](/quest/m0/request-caps.md) - lite message sizes, IETF request IDs, and per-session announces and subscriptions are bounded - [quest check everywhere](/quest/m0/quest-check-everywhere.md) - `quest check` guards `main`, `dev`, and the line branches on push and PR, not only PRs into `main` - [noq reassembly cap](/quest/m0/noq-reassembly-cap.md) - noq carries quinn's stream reassembly cap and the connection receive window is finite by default - [qmux reset race](/quest/m0/qmux-reset-race.md) - qmux handles RESET_STREAM under one lock instead of panicking @@ -43,15 +48,11 @@ Published API or wire breaks still land on dev; each quest's Plan says so. - [Shared fronts](/quest/m0/shared-fronts.md) - viewer sessions share a front, so fronts scale with peers, not viewers - [Frame alloc budget](/quest/m0/frame-alloc-budget.md) - frame buffers pre-allocate within a per-session budget and otherwise grow with bytes received - [Handshake deadline](/quest/m0/handshake-deadline.md) - an unfinished handshake or slow HTTP header times out -- [Request caps](/quest/m0/request-caps.md) - lite message sizes, IETF request IDs, and per-session announces and subscriptions are bounded - [Subscriber prune](/quest/m0/subscriber-prune.md) - a track's subscription list holds only live subscribers - [Revalidate overflow](/quest/m0/revalidate-overflow.md) - no auth duration can overflow a deadline and abort the relay -- [Legal IETF input](/quest/m0/ietf-legal-input.md) - draft-20+ FETCH, allowed parameters, INCLUDE_PROPERTIES and FORWARD=0 decode and are refused per request, not session-fatal -- [IETF FIN semantics](/quest/m0/ietf-fin-not-cancel.md) - a request stream FIN stops updates without cancelling, and REQUEST_UPDATE on a subscribe is parsed - [Wildcard](/quest/m0/wildcard/README.md) - a relay resolves subscriptions against advertised prefixes, a service claims the prefix it could serve and refuses the rest instead of enumerating broadcasts, and the browser player treats a covering claim as availability - [Audio quality harness](/quest/m0/audio-quality-harness/README.md) - a browser playout latency regression fails a nightly run instead of arriving as a bug report, and its recorder supplies the jitter target's replay traces - [Audio jitter target](/quest/m0/audio-jitter-target/README.md) - the audio playout target is a measured estimate of arrival timing in both languages, not a round-trip guess -- [A/V clock](/quest/m0/plan-av-clock.md) - the audio playhead drives Sync.reference while audio plays, through per-track sync handles ## Related diff --git a/quest/m0/audio-jitter-target/README.md b/quest/m0/audio-jitter-target/README.md index 9ccd989e69..5e99fd83fb 100644 --- a/quest/m0/audio-jitter-target/README.md +++ b/quest/m0/audio-jitter-target/README.md @@ -15,7 +15,7 @@ Boundaries: convergence still uses skip-ahead and silence, so playing slightly faster or slower to converge stays [Time stretch](/quest/m1/watch-audio-time-stretch.md). No packet loss concealment. Video keeps its own target; making the audio playhead the clock is [Plan: A/V -clock](/quest/m0/plan-av-clock.md). +clock](/quest/m1/av-clock.md). ## Plan @@ -87,4 +87,3 @@ buffer against uneven arrivals. - [Audio quality harness](/quest/m0/audio-quality-harness/README.md) - the automated proof, and the recorder of the traces the watch quest replays - [Time stretch](/quest/m1/watch-audio-time-stretch.md) - inaudible convergence, on top of this -- [Plan: A/V clock](/quest/m0/plan-av-clock.md) - the clock this target eventually feeds diff --git a/quest/m0/audio-jitter-target/watch.md b/quest/m0/audio-jitter-target/watch.md index a1a258f61e..0151ecdd38 100644 --- a/quest/m0/audio-jitter-target/watch.md +++ b/quest/m0/audio-jitter-target/watch.md @@ -92,13 +92,9 @@ The branch also replaces `probe` in `SyncInput` with per-track `audioSpread` and `videoSpread` inputs, which breaks the published `@moq/watch` type. This quest lands on `main`, so it adds the spread inputs beside `probe` and stops reading `probe`; removing it is part of the `SyncInput` reshape in -[Plan: A/V clock](/quest/m0/plan-av-clock.md). Land the estimator so +[Plan: A/V clock](/quest/m1/av-clock.md). Land the estimator so that quest can adopt it without a second estimator change. ## Required - [Browser harness](/quest/m0/audio-quality-harness/browser.md) - records the arrival traces this quest replays - -## Related - -- [Plan: A/V clock](/quest/m0/plan-av-clock.md) - reshapes `SyncInput` around the per-track spread this quest produces diff --git a/quest/m0/ietf-fin-not-cancel.md b/quest/m0/ietf-fin-not-cancel.md index 329cac16ee..0f1d880919 100644 --- a/quest/m0/ietf-fin-not-cancel.md +++ b/quest/m0/ietf-fin-not-cancel.md @@ -28,5 +28,4 @@ Public API: none. Wire: conformance fix; no draft change. ## Related -- [Legal IETF input](/quest/m0/ietf-legal-input.md) - the other interop blocker - [Lite request streams](/quest/m1/request-stream-serve.md) - lite deliberately treats a FIN as ending the request; don't unify the two diff --git a/quest/m0/ietf-legal-input.md b/quest/m0/ietf-legal-input.md index 1faf8828d1..c6f761deb2 100644 --- a/quest/m0/ietf-legal-input.md +++ b/quest/m0/ietf-legal-input.md @@ -41,8 +41,3 @@ choice, since honoring them would change what existing peers receive. - Mirror the decode in `js/net`. Public API: none. Wire: fixes conformance; no draft change. - -## Related - -- [IETF FIN semantics](/quest/m0/ietf-fin-not-cancel.md) - the other interop blocker -- [IETF stream types](/quest/m1/ietf-uni-stream-types.md) - same stream-scoped-before-fatal rule for uni streams diff --git a/quest/m0/ietf-subgroup-refusal.md b/quest/m0/ietf-subgroup-refusal.md new file mode 100644 index 0000000000..06fd256b07 --- /dev/null +++ b/quest/m0/ietf-subgroup-refusal.md @@ -0,0 +1,22 @@ +# [S] Subgroup refusal stays on the stream + +## Goal + +A peer that sends a non-zero subgroup on moq-transport loses that one stream, +never the session. Every other track on the session keeps flowing. + +## Plan + +Against moxygen's `moqtest_server`, a track with two subgroups per group ended +the relay's upstream session, and the server reconnected. Our side refuses the +stream today. Whether the session ends because of how we refuse it (the reset +code, STOP_SENDING, or the alias state it leaves behind) or because the peer +reacts badly to a correct refusal is not known yet. Reproduce it first. If the +peer is at fault, say so on its tracker and keep a regression test for our side. + +A test with an IETF peer that sends a subgroup 1 stream next to a healthy track +is the check. + +## Related + +- [Moxygen compatibility](/quest/m1/moxygen/README.md) - subgroups stay out of scope; only the blast radius is in diff --git a/quest/m1/ietf-uni-stream-types.md b/quest/m0/ietf-uni-stream-types.md similarity index 100% rename from quest/m1/ietf-uni-stream-types.md rename to quest/m0/ietf-uni-stream-types.md diff --git a/quest/m0/noq-reassembly-cap.md b/quest/m0/noq-reassembly-cap.md index db3e5456fc..ae7d7e9e4b 100644 --- a/quest/m0/noq-reassembly-cap.md +++ b/quest/m0/noq-reassembly-cap.md @@ -31,5 +31,4 @@ Public API: none. Wire: a peer that exceeds the chunk cap is closed. ## Related -- [QUIC release](/quest/m1/quic/release.md) - owns the fork's security-update procedure this gap shows is missing - [Peer limits](/quest/m1/quic/peer-limits.md) - per-peer windows and stream limits diff --git a/quest/m0/qmux-reset-race.md b/quest/m0/qmux-reset-race.md index 6b6143f59a..901b63dae0 100644 --- a/quest/m0/qmux-reset-race.md +++ b/quest/m0/qmux-reset-race.md @@ -21,7 +21,3 @@ no-op, not a panic. here. Moving to 0.6 is not part of this quest. Public API: none. Wire: none. - -## Related - -- [qmux on noq-proto](/quest/m1/quic/qmux.md) - replaces these stream maps entirely, later diff --git a/quest/m0/remove-gossip.md b/quest/m0/remove-gossip.md index 58901b1395..cf99b23543 100644 --- a/quest/m0/remove-gossip.md +++ b/quest/m0/remove-gossip.md @@ -31,7 +31,3 @@ through `--cluster-connect-api` and never enables gossip. Public API: removes a relay config field and flag. Wire: relays stop announcing `.internal/origins`. - -## Related - -- [Cluster routing](/quest/m1/cluster-routing.md) - takes its topology from configured links only diff --git a/quest/m0/shared-fronts.md b/quest/m0/shared-fronts.md index f2594ad9ff..21d5a8af70 100644 --- a/quest/m0/shared-fronts.md +++ b/quest/m0/shared-fronts.md @@ -30,4 +30,3 @@ Public API: none. Wire: none. - [Front deadlines](/quest/m1/front-deadline-index.md) - per-front cost per track - [Front parking](/quest/m1/origin-front-parks.md) - also changes what mints a front -- [Relay memory](/quest/m1/relay-memory.md) - per-announcement measurements diff --git a/quest/m0/wildcard/README.md b/quest/m0/wildcard/README.md index 97be3a60c4..334622caa2 100644 --- a/quest/m0/wildcard/README.md +++ b/quest/m0/wildcard/README.md @@ -140,7 +140,7 @@ field. refused. A prefix wider than the grant is accepted, but it only routes requests for paths the grant covers. Fleet-wide services use the cluster identity; a customer service serves only what its own v1 grant contains. - Until [Advertise-only authorization](/quest/m1/processor/advertise-auth.md) + Until [Advertise-only authorization](/quest/m2/processor/advertise-auth.md) lands, the publish scope stands in for advertising; a credential with its own advertise scope is checked against that instead. - **Claims are visible to subscribers.** A subscriber sees every advertised @@ -219,13 +219,7 @@ distinguish recording generations reads the catalog's archive entry ## Related -- [path-patterns](/quest/m1/path-patterns.md) - owns the pattern dialect - and the shared matcher tokens and filters reuse - [archive](/quest/m1/archive/README.md) - an archive claims the root, and its catalog names the generations a claim cannot -- [Cluster routing](/quest/m1/cluster-routing.md) - origin selection by cost - with an HRW tie-break, built on this line's longest-prefix rule - [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - derived output mirrors the source path, `@` segment included -- [Announcement shapes](/quest/m3/announce-shapes.md) - moq-lite-only exact, - suffix, and prefix+suffix claims that survive relay hops diff --git a/quest/m1/2964-quic-workers-dropping-one-split-server-resizes-the.md b/quest/m1/2964-quic-workers-dropping-one-split-server-resizes-the.md deleted file mode 100644 index 891110d96d..0000000000 --- a/quest/m1/2964-quic-workers-dropping-one-split-server-resizes-the.md +++ /dev/null @@ -1,35 +0,0 @@ -# [M] Integrate the hardened reuseport group with QUIC workers - -## Goal - -The owning tokio worker group uses `moq_sock::shard::Group`'s enforced -formation and socket-retention contract. Complete and retained socket groups -preserve connection-ID steering through partial startup and member teardown. - -## Plan - -The published `Workers::split` ownership change and functional group shutdown -have landed. Do not repeat -that API redesign here. This follow-up replaces the worker's private lifetime -bookkeeping with the hardened socket-group primitive. - -Adopt moq-sock's claims and complete-group ownership. No member serves before -the final bind and filter attachment, and every socket stays owned until -serving has stopped for the group. Preserve the dev owner, shutdown, failure -propagation, and worker-local builder contract without another published -signature change. - -Use Linux runtime regressions to exercise incomplete startup, a failed final -bind, dropping an unused server handle, and a serving member completing or -failing. Check the socket group and connection-ID steering on a surviving -session while unused handles are dropped, and prove all serving stops when -the group terminates. Wire the tests into normal or nightly CI. - -Public API: no further `moq-tokio` ownership change. The hardened group -already exists (`Group::bind` and `Group::complete` over `Claim` in -`rs/moq-sock/src/shard.rs`). Wire: no format change. Close #2964 only when -both the dev ownership proof and this integration are complete. - -## Closes - -- [#2964](https://github.com/moq-dev/moq/issues/2964) - close this issue when the quest finishes diff --git a/quest/m1/README.md b/quest/m1/README.md index 9501773df5..9d4df1e022 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -17,43 +17,37 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. ## Required -- [Cluster routing](/quest/m1/cluster-routing.md) - an announcement says where a broadcast originates, not how to reach it, and a relay hears only the prefixes its clients asked for +- [Cluster routing](/quest/m1/cluster-routing/README.md) - an announcement says where a broadcast originates, not how to reach it, and a relay hears only the prefixes its clients asked for +- [A/V clock](/quest/m1/av-clock.md) - the audio playhead drives Sync.reference while audio plays, through per-track sync handles - [Track tail interop](/quest/m1/track-tail-interop.md) - a Rust publisher ending a track with a group in flight is read to its end by the JS subscriber, and the reverse, in `just test interop` - [Binding audio delay](/quest/m1/binding-surface.md) - moq-ffi and every wrapper configure and observe audio playout delay - [FFI shape](/quest/m1/ffi-shape/README.md) - the bindings mirror Rust's layers: net at the root, then media, json, flate, audio, and video namespaces built from the handle below -- [Track demand](/quest/m1/track-demand.md) - Rust and JS watch a track's subscribers through `demand()` alone -- [Session close](/quest/m1/session-close.md) - a graceful session end withdraws announces and waits one second for the ack -- [Graceful close in bindings](/quest/m1/bindings-graceful-close.md) - on dev, `shutdown` drains a session in moq-ffi and every wrapper like Rust, so the wrappers keep the tail of a publish -- [Close codes](/quest/m1/close-codes.md) - a client sees the peer's application close code over WebSocket and raw QUIC, like WebTransport - [Raw stream codes](/quest/m1/raw-stream-codes.md) - raw QUIC stream resets and stops carry the application's code, not an HTTP/3-mapped one +- [Mobile ownership](/quest/m1/mobile-ownership.md) - decide whether Rust or platform code owns mobile capture, codecs, and rendering +- [IETF drain before close](/quest/m1/ietf-drain-before-close.md) - moq-transport sessions deliver finished tracks before a graceful close, as moq-lite does +- [Demo serve-hls renditions](/quest/m1/serve-hls-renditions.md) - `just pub serve-hls` serves 720p and 144p instead of two 256-wide copies +- [moq play drain tail](/quest/m1/play-drain-tail.md) - retired renditions and finite tracks play their last 10 ms of audio - [WebTransport close upstream](/quest/m1/wt-close-upstream.md) - web-transport-moq delivers the close capsule itself, and moq-tokio's `CLOSE_LINGER` is deleted -- [Watch refusal](/quest/m1/watch-refusal.md) - `` shows an origin refusal as an error instead of sitting offline - [Resumed groups](/quest/m1/resume-latest.md) - a half-delivered group ends once the new copy is past it, so a group-only reader never parks after a mid-group failover - [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - every stream group in a lite subscription arrives or is dropped by name, and lite-07 drops its stream count for it - [Parked reads wake](/quest/m1/parked-read-wakes.md) - a read parked on an evicted or aborted group wakes and re-judges, for resume successors and plain tracks alike - [Reader end parity](/quest/m1/reader-end-parity.md) - JS readers see a track's end once the newest group reaches the declared end, as Rust readers do - [Cross-relay bursts](/quest/m1/cross-relay-bursts.md) - bursty small-group tracks cross two relays without lost groups, unanswered FETCHes, or stalls - [Request stream cancel](/quest/m1/request-stream-serve.md) - a lite publisher stops resolving a SUBSCRIBE or FETCH once the requester FINs or resets, through one wrapper that owns every request stream's reader -- [JS closed-track leak](/quest/m1/js-closed-track-leak.md) - on dev, a subscriber that joins a closed JS track with unlimited retention is released instead of cached forever - [Session death parity](/quest/m1/session-death.md) - a local close ends tracks cleanly in both languages, and JS group readers see the session's error on session death - [Watch and publish under CSP](/quest/m1/csp-assets.md) - blob workers stay the default; strict-CSP apps host the files and set a base URL - [More tests under load](/quest/m1/test-flakes-2.md) - the second round of load-only failures, fixed at the cause - [CI runner stalls](/quest/m1/ci-runner-stalls.md) - the 0.4 to 0.8 s freezes of both interop tracks on CI are attributed from a week of nightlies and fixed or told apart from playback bugs - [Catalog estimate rate](/quest/m1/catalog-estimate-rate.md) - a rising `jitter`/`delay` estimate republishes the catalog at most once a second, in js/publish and moq-mux - [Legacy end overshoot](/quest/m1/legacy-end-overshoot.md) - browser playback survives a group that starts inside the previous group's estimated end -- [UnknownSession log flood](/quest/m1/unknown-session-logs.md) - streams reset before their WebTransport header stop being reported as UnknownSession at WARN - [Merge queue](/quest/m1/merge-queue.md) - the required checks run on `merge_group`, so a stale green check can no longer break main - [Wire compatibility](/quest/m1/wire-compat.md) - a nightly run tests this checkout against the last published release for tokens, session wire, and catalog/container - [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - unflagged loop wraps move audio and video by one shift, so A/V sync holds across wraps - [TS PSI reassembly](/quest/m1/ts-psi-reassembly.md) - `import ts` reads a PAT or PMT that spans packets or follows a nonzero pointer_field instead of aborting, and one corrupted section costs a repetition and a counted `CRC_error`, not the import - [TS stats module](/quest/m1/ts-stats-module.md) - on dev, the TS stats types move under `ts::stats` as `Snapshot` and `Stream`, with an owned `track` - [Same-hop importers](/quest/m1/hop-aligned-import.md) - importers sharing a `--hop` and fed one stream publish identical groups and timestamps, so failover survives -- [Capture control](/quest/m1/capture-control.md) - on dev, `encode::Capture` replaces `CaptureOptions` without a `clock` field (it reads the catalog's), an unsupported `cut()` errors, and dropping the last `Control` cancels in-flight opens - [Capture by default](/quest/m1/capture-default.md) - moq-video and moq-audio build `capture` by default, so pre-merge checks test it and the capture gate goes away -- [Video surface](/quest/m1/video-surface.md) - on dev, moq-ffi's `native` becomes `surface`, refused on platforms with no surface -- [HLS discontinuity sequence](/quest/m1/hls-discontinuity-sequence.md) - on dev, `Segment::discontinuity` is the absolute sequence, so every cursor agrees - [Auth client CA](/quest/m1/relay-auth-client-ca.md) - on dev, `auth::Config::validate` and `init` take the client-CA flag, so no caller can skip the check -- [Remove live()](/quest/m1/remove-live.md) - on dev, importers publish stream timestamps verbatim, the catalog clock maps them to wall time, and an encoder restart becomes a new epoch - [Data track clock](/quest/m1/data-track-clock.md) - JSON and binary data tracks stamp on the catalog's clock at write time, matching the media's anchored clock - [Go and Dart doc samples](/quest/m1/doc-samples-go-dart.md) - Go and Dart doc samples compile against their wrappers - [Data capture in bindings](/quest/m1/data-capture-bindings.md) - moq-ffi and every wrapper pass a data frame's capture time, and the JSON window producer takes one @@ -64,7 +58,6 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [JavaScript FETCH](/quest/m1/js-fetch.md) - generic on-demand group serving and IETF FETCH for browser publishers - [Archive](/quest/m1/archive/README.md) - record selected tracks to any object_store and replay them over FETCH or derived HLS; the catalog entry and format may break in place, since no archives exist - [Tooling](/quest/m1/tooling/README.md) - justfiles become a one-line menu over `sh/`, one impact map scopes CI, and every workflow step runs a recipe -- [Path patterns](/quest/m1/path-patterns.md) - one matcher for every predicate over broadcast paths: tokens, origins, interest - [In-band auth](/quest/m1/auth/README.md) - a session tells its peer what it may publish and subscribe to, unions tokens presented in band, and fails loud on an out-of-scope publish - [Dropped sources](/quest/m1/dropped-sources.md) - track consumers see the producer's real error on every end path, never `Dropped` - [Shaper virtual time](/quest/m1/shaper-virtual-time.md) - `moq-shaper` tests judge seeded decisions on paused time, not on wall-clock delivery under load @@ -86,62 +79,44 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Drain](/quest/m1/drain/README.md) - relay restarts drain sessions over GOAWAY instead of hard-dropping them - [Strict Redirect::resolve](/quest/m1/redirect-resolve.md) - on dev, `Redirect::resolve` can no longer quietly turn a refused redirect into a redial - [Transport upgrade](/quest/m1/transport-upgrade/README.md) - a session that came up over WebSocket moves to QUIC once the QUIC dial lands, handing over at a group boundary -- [P2P](/quest/m1/p2p/README.md) - opted-in clients serve each other over data channels and iroh while the relay stays the rendezvous and the fallback, under application policy -- [One port](/quest/m1/one-port/README.md) - a relay speaks QUIC, STUN, WebRTC media, and SRT on one UDP port and HTTP, RTMP, and RTMPS on one TCP port -- [Signed priority](/quest/m1/signed-priority.md) - on dev, every API priority is an `i8` with 0 as the unset midpoint, and hang's built-ins sit above it - [Scope track priority](/quest/m1/track-priority-scope.md) - priority orders one owner's streams, and a shared cluster session is fair across tenants -- [Stream sessions](/quest/m1/uring-tcp/README.md) - serve WebSocket and HTTP from the io_uring workers, where io_uring pays off most - [IETF on the ring](/quest/m1/uring-ietf.md) - the io_uring workers serve moq-transport sessions too, so a uring relay drops no client protocol - [BBR ACK cleanup](/quest/m1/bbr-ack-cleanup.md) - packet bookkeeping scales with completed entries instead of scanning the flight on every ACK - [Perf](/quest/m1/perf/README.md) - eliminate measured hot-path costs across moq-uring, kio, and the moq-net model - [#2924](/quest/m1/2924-moq-relay-tls-rotation-is-not-atomic-across-thread-per.md) - every listener on both runtimes shares one reloadable served identity, so rotation is atomic and generate works with workers -- [#2964](/quest/m1/2964-quic-workers-dropping-one-split-server-resizes-the.md) - integrate the dev worker owner with hardened socket-group formation - [Benchmark regressions in CI](/quest/m1/bench-ci.md) - PRs get a non-blocking comparison of the Criterion benches they affect, and a nightly trend on main alerts on regressions - [Benchmark comparisons](/quest/m1/performance-comparisons.md) - retained evidence, repeated paired runs, and uncertainty for performance claims - [#3126](/quest/m1/3126-moq-bench-every-readme-example-fails-to-parse-and.md) - moq-bench reports per-interval latency percentiles so the ramp leaves the steady state - [Relay session bench](/quest/m1/bench-relay.md) - the same scenario through moq-relay's own connection handling -- [Bench coverage](/quest/m1/bench-coverage.md) - Criterion targets for moq-mux containers, the hang catalog, moq-auth verification, and moq-pattern matching -- [Stats producer bench](/quest/m1/stats-producer-bench.md) - the stats drain and encode cost per tick, swept over held paths and tiers and run nightly - [Relay profiling](/quest/m1/performance-profiles.md) - reproducible CPU and allocation captures under the existing workloads - [Browser benchmarks](/quest/m1/browser-benchmarks.md) - measure JS transport, container, decode, and render costs in an identified browser - [Generated @moq/net](/quest/m1/rs2ts/README.md) - the browser runs moq-net as TypeScript generated from the Rust source, retiring js/net's hand-written protocol and model code - [Plan: watch worker](/quest/m1/plan-watch-worker.md) - prototype an invisible page worker against app-spawned workers, and land the jank harness that decides - [Watch worker](/quest/m1/watch-worker.md) - watch playback runs in a worker onto an OffscreenCanvas, so main-thread jank never stalls video or audio -- [Closure counters](/quest/m1/closure-counters.md) - a departed node's return never regresses the closure counters a consumer already saw - [RTMP interleaving](/quest/m1/rtmp-interleaving.md) - isolate partial messages before optimizing assembly copies - [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - with the default pool, relay memory plateaus at the expiry window on every version - [Plan: cache age-out](/quest/m1/cache-wall-eviction.md) - a swept benchmark decides whether the track cache ages groups out on wall time without a write - [Frame slot charge](/quest/m1/frame-slot-charge.md) - a group's frame slots past the first four count against the cache pool, including capacity a released group keeps -- [Relay memory](/quest/m1/relay-memory.md) - remeasure what an announcement costs after prefix routes - [Front deadlines](/quest/m1/front-deadline-index.md) - a front's per-event cost stops growing with its track count: an expiry index and per-track wakes, proven by a churn benchmark - [Front parking](/quest/m1/origin-front-parks.md) - an unroutable request waits on a front instead of re-asking on every route-table move - [Publish channel count](/quest/m1/publish-audio-channel-count.md) - forcing a channel count on an Audio.Capture stops costing the subscriber gaps of silence - [JS abandonment](/quest/m1/js-subscribe-abandonment.md) - a viewer returning during IETF subscribe setup keeps its track across microtasks -- [IETF stream types](/quest/m1/ietf-uni-stream-types.md) - padding streams are discarded stream-only and an unknown uni type closes the session, per draft-21 - [Epoch primitive](/quest/m1/epoch.md) - one `Epoch` type in moq-net and @moq/net, carried as a trailing `@` path segment, shared by e2ee and broadcast epochs - [E2EE](/quest/m1/e2ee/README.md) - TypeScript and Rust peers interoperate over encrypted broadcasts no relay can decrypt - [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - each publish of a name gets a fresh `@` epoch, viewers follow the newest live one at once, and bare names still resolve on every version -- [Processor](/quest/m1/processor/README.md) - a customer-run worker publishes an on-demand contribution under its own service prefix with scoped access - [#3056](/quest/m1/3056-watch-video-decoder-captures-the-rewind-generation-at.md) - watch: the video decoder resets on a declared discontinuity - [#933](/quest/m1/933-video-rotation-metadata-not-propagated-from-mobile-camera.md) - the catalog rotation follows the live camera's orientation - [#2848](/quest/m1/2848-follow-the-bandwidth-grant-in-moq-audio-instead-of.md) - the Opus producer follows its bandwidth grant through the settled `moq_mux::rate::Control` -- [Ladder](/quest/m1/ladder/README.md) - a transcode ladder adapts to the uplink it publishes over, instead of encoding every live rung at its ceiling - [Time stretch](/quest/m1/watch-audio-time-stretch.md) - js/watch: the audio ring converges by time-stretching instead of skipping or going silent - [Native audio quality](/quest/m1/audio-quality-native.md) - the browser lane's profiles, budgets, and metric schema run against `moq play` on a dummy device -- [fMP4 emsg](/quest/m1/emsg.md) - event messages survive fMP4 import, and the timed-metadata contract ID3, SCTE-35, and FLV script tags share is settled with them -- [#2279](/quest/m1/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 - [Caption import](/quest/m1/captions-import.md) - fMP4 and MKV subtitle tracks import as text renditions instead of erroring or being dropped - [MSF caption roles](/quest/m1/captions-msf.md) - an MSF caption, subtitle, or sign-language track survives conversion to a hang catalog -- [CEA-608/708](/quest/m1/captions-cea.md) - captions carried inside video SEI become a real text rendition at import - [Colour model](/quest/m1/color-model.md) - the catalog describes a rendition's colour and HDR properties instead of leaving a TODO - [Open-GOP leading pictures](/quest/m1/open-gop-leading-pictures.md) - a viewer joining at a recovery point drops the leading pictures it cannot decode; continuous viewers keep them - [Catalog warmup](/quest/m1/catalog-warmup.md) - `warmup` on video and audio renditions, in the catalog and the draft - [Audio warmup](/quest/m1/audio-warmup.md) - a viewer joining an Opus rendition mid-stream never hears the unconverged first 80 ms - [#3021](/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md) - GStreamer maps every pad onto one continuous broadcast clock across source restarts - [TS byte schedule](/quest/m1/ts-export-byte-schedule.md) - moq export ts places PCRs and padding on the byte grid `mpegts.muxRate` implies, so a receiver can clock off arrival -- [Text availability](/quest/m1/text-schema.md) - a text track publishes its own coverage index instead of copying the media timeline -- [ID3 catalog section](/quest/m1/id3.md) - timed ID3 as a first-class container-neutral catalog section -- [FLV script tags](/quest/m1/flv-script.md) - onMetaData and AMF data messages survive RTMP and FLV import - [Release profile](/quest/m1/release-profile.md) - every release build gets fat LTO, one codegen unit, and stripping from the workspace profile instead of three script exports - [Size report](/quest/m1/size-report.md) - a nightly job reports every shipped artifact's size, native and JS, and alerts when one grows - [Publish lazy file source](/quest/m1/publish-lazy-file.md) - a camera or screen `` stops downloading mediabunny's ~99 KB gzip @@ -155,12 +130,8 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Dart publish](/quest/m1/dart-publish.md) - the packages are built and dry-run clean but exist nowhere consumers can install from - [Dart codec parity](/quest/m1/dart-codecs.md) - Dart is the one binding that cannot originate media - [#2850](/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md) - js/net: decode messages synchronously from buffered bytes and delete the publisher read-ahead queue -- [Install moq](/quest/m1/moq-installer.md) - one command installs or upgrades the released CLI on macOS and Linux -- [Install URL](/quest/m1/moq-install-url.md) - moq.dev serves the canonical installer at /install.sh -- [`moq relay`](/quest/m1/moq-relay-subcommand.md) - the relay runs under a `moq` verb with its own flags and TOML, while `moq-relay` stays a minimal binary - [`moq --listen` admission](/quest/m1/cli-serve.md) - a listening CLI session is authenticated, scoped, counted, and drained like a relay's instead of accepting everything - [#709](/quest/m1/709-automatic-letsencrypt-support.md) - the relay provisions and renews its own ACME certificate through rustls-acme over TLS-ALPN-01, persisted on disk - [Audio capture without ALSA link](/quest/m1/capture-alsa-link.md) - moq-audio capture and playback build on Linux without linking libasound - [Ship capture and playback](/quest/m1/cli-packaging.md) - a released moq binary can capture and play, which no distribution currently enables - [io_uring flow control](/quest/m1/uring-flow-control-windows.md) - the relay's io_uring workers honor the QUIC flow-control windows instead of refusing them -- [Remove effect.cancel](/quest/m1/effect-cancel.md) - `@moq/signals` drops the deprecated `Effect.cancel` on dev diff --git a/quest/m1/archive/README.md b/quest/m1/archive/README.md index dd334aa40b..28cf3ecec7 100644 --- a/quest/m1/archive/README.md +++ b/quest/m1/archive/README.md @@ -114,9 +114,7 @@ owned by that prerequisite, not duplicated in archive storage. - [Recording writer](/quest/m1/archive/writer.md) - feed the segmenter from a `broadcast::Consumer`, store each segment, then commit its record - [Recording reader](/quest/m1/archive/reader.md) - serve archived FETCH through a supplied `broadcast::Producer` -- [Paced replay](/quest/m1/archive/paced-replay.md) - a replay pushes its groups to live subscribers on one shared clock, so any live player plays it - [Replay provenance](/quest/m1/archive/provenance.md) - a replay's catalog names its timeline, replay path, store URL, and format version -- [Browser archive](/quest/m1/archive/browser.md) - the same contract for browser-published broadcasts - [Offline archive HLS](/quest/m1/archive/hls.md) - render playlists from the archive timeline and fetch segment media lazily - [DVR rewind](/quest/m1/archive/dvr.md) - seek through a bounded archive and return to live playback - [Enrollment flake](/quest/m1/archive/enrollment-flake.md) - the opening-snapshot test waits for real enrollment, not the `.info` file diff --git a/quest/m1/audio-codecs/README.md b/quest/m1/audio-codecs/README.md index e6051c995d..e9c3cf198b 100644 --- a/quest/m1/audio-codecs/README.md +++ b/quest/m1/audio-codecs/README.md @@ -62,7 +62,6 @@ ready now. ## Related - [OBS native codecs](/quest/m1/obs-moq-video/README.md) - the OBS source and encoder adapters consume this through moq-ffi; #3498 narrowed OBS to what moq-audio decodes today -- [Runtime QA hosts](/quest/m2/runtime-qa-hosts.md) - Windows and Android verification needs a host; the Windows and macOS CI gates only compile - [Dart codec parity](/quest/m1/dart-codecs.md) - Dart gains these once it builds with the `audio` feature - [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - Windows decodes HE-AAC, multichannel AAC, and what else the MFTs offer - [Media Foundation encode](/quest/m2/audio-encode-mediafoundation.md) - Windows encodes AAC-LC diff --git a/quest/m1/auth/README.md b/quest/m1/auth/README.md index ab481d4558..78ddbe011a 100644 --- a/quest/m1/auth/README.md +++ b/quest/m1/auth/README.md @@ -120,7 +120,5 @@ existing lite-06 ALPN. ## Related -- [Pattern interest](/quest/m1/path-patterns.md) - moves AUTH's legacy grant prefixes to patterns; ANNOUNCE_REQUEST stays a prefix -- [Expiring media grants](/quest/m1/processor/grant-lease.md) - a worker's +- [Expiring media grants](/quest/m2/processor/grant-lease.md) - a worker's lease renewal is a new in-band token -- [P2P](/quest/m1/p2p/README.md) - the first consumer of hop-bound peer grants diff --git a/quest/m1/auth/lite.md b/quest/m1/auth/lite.md index 6d54e56217..2c5eb5add9 100644 --- a/quest/m1/auth/lite.md +++ b/quest/m1/auth/lite.md @@ -144,8 +144,3 @@ app-added token does not suspend the check; a reset AUTH stream reports cross-language harness. On main, additive. - -## Related - -- [Pattern interest](/quest/m1/path-patterns.md) - moves AUTH's grant - prefixes to patterns; ANNOUNCE_REQUEST stays a prefix diff --git a/quest/m1/auth/peer-grant.md b/quest/m1/auth/peer-grant.md index 7a839443d8..bdf539b257 100644 --- a/quest/m1/auth/peer-grant.md +++ b/quest/m1/auth/peer-grant.md @@ -46,7 +46,7 @@ serves only the granted paths. A missing, expired, HMAC-signed, or unproven grant is not a grant. P2P is the first consumer: -[Signaling and policy](/quest/m1/p2p/signal.md) presents the grant in band +[Signaling and policy](/quest/m2/p2p/signal.md) presents the grant in band on each direct session. This quest does not depend on that line. Docs: `doc/bin/relay/auth.md` states that peer grants need an asymmetric @@ -66,4 +66,4 @@ Additive. ## Related -- [Signaling and policy](/quest/m1/p2p/signal.md) - the first consumer +- [Signaling and policy](/quest/m2/p2p/signal.md) - the first consumer diff --git a/quest/m1/auth/relay-refresh.md b/quest/m1/auth/relay-refresh.md index cff68ecc51..65fa8ddc8b 100644 --- a/quest/m1/auth/relay-refresh.md +++ b/quest/m1/auth/relay-refresh.md @@ -81,7 +81,6 @@ Additive. ## Required - [Origin narrowing](/quest/m1/auth/narrowing.md) - the live re-scope a shrinking token union needs, so no temporary close-on-shrink policy ships -- [Pattern interest](/quest/m1/path-patterns.md) - AUTH can represent the complete grants relay revalidation returns - [Lite stream](/quest/m1/auth/lite.md) - supplies the AUTH stream and `auth::Request` this consumes - [Unauthorized reset](/quest/m1/auth/unauthorized.md) - the code this diff --git a/quest/m1/auth/request-token.md b/quest/m1/auth/request-token.md index ddf6406012..7f501b10d0 100644 --- a/quest/m1/auth/request-token.md +++ b/quest/m1/auth/request-token.md @@ -73,6 +73,5 @@ to) and on `moq_auth::Client` (the per-request lease). Wire: none new; the param ## Required -- [Legal IETF input](/quest/m0/ietf-legal-input.md) - decodes the parameter everywhere the draft allows it - [Relay tokens](/quest/m1/auth/relay-refresh.md) - supplies the lease revalidation the per-request lease reuses diff --git a/quest/m0/plan-av-clock.md b/quest/m1/av-clock.md similarity index 100% rename from quest/m0/plan-av-clock.md rename to quest/m1/av-clock.md diff --git a/quest/m1/bbr-ack-cleanup.md b/quest/m1/bbr-ack-cleanup.md index 3439bf801e..082c39ae3b 100644 --- a/quest/m1/bbr-ack-cleanup.md +++ b/quest/m1/bbr-ack-cleanup.md @@ -55,6 +55,5 @@ is needed. ## Related -- [Loss sampling](/quest/m1/quic/bbr-loss-parity.md) - preserve packet metadata needed by the separate loss-sample repair; both edit `bbr3/mod.rs`, so sequence them - [BBR starvation edges](/quest/m1/quic/bbr-app-limited-edges.md) - also edits `bbr3/mod.rs`; one owner there at a time - [Benchmark comparisons](/quest/m1/performance-comparisons.md) - reusable measurement guidance, not a prerequisite for this fix diff --git a/quest/m1/bench-ci.md b/quest/m1/bench-ci.md index 4df11aa246..cc3d7c8bbd 100644 --- a/quest/m1/bench-ci.md +++ b/quest/m1/bench-ci.md @@ -50,4 +50,3 @@ a GitHub App: ## Related - [Benchmark comparisons](/quest/m1/performance-comparisons.md) - extends the same `bench/run.sh` with repeated paired rounds -- [Bench coverage](/quest/m1/bench-coverage.md) - more targets for this job to track diff --git a/quest/m1/bindings-graceful-close.md b/quest/m1/bindings-graceful-close.md deleted file mode 100644 index adfa57fac5..0000000000 --- a/quest/m1/bindings-graceful-close.md +++ /dev/null @@ -1,15 +0,0 @@ -# [M] Bindings close sessions gracefully - -## Goal - -On dev, `shutdown` in moq-ffi and every wrapper (py, swift, kt, go, dart, and the generated C/C++) drains the session the way `moq_net::Session::close` and `moq_tokio::Connection::close` do. Finished tracks deliver their last groups and FIN before the session ends, bounded by the same deadline. The language bindings stop losing the tail of a publish when they stop. OBS gets this only once it moves off hand-written libmoq onto the generated C++, which [C++ through moq-ffi](/quest/m1/cpp/README.md) owns; libmoq takes no more shutdown work. - -## Plan - -`Session::close` and `Connection::close` landed with drain-before-close (#4430). Today the FFI session (`rs/moq-ffi/src/session.rs`) only has `cancel(code)` and `shutdown()`, and `shutdown` is just `cancel(0)`, which ends it immediately. - -Decision (maintainer, 2026-09-28): `shutdown` becomes the async draining close in every binding. The name can't be `close`, because UniFFI's Kotlin generator already emits `AutoCloseable.close()` to release the handle, and `shutdown` already promises a graceful shutdown. Turning it async is breaking, so this targets `dev`; no second method, per the no-shim rule in AGENTS.md. `cancel` stays the immediate path. Like `Session::close`, `shutdown` is fallible: a drain the peer does not acknowledge in time surfaces as a timeout error through each binding's error mechanism rather than a silent success. Test both paths end to end through moq-ffi: a final group and clean track end reach the peer after `shutdown` returns, and an unacknowledged drain returns the timeout. - -Mirror `shutdown` in each wrapper and follow the Cross-Package Sync table in AGENTS.md, including `doc/lib/*`, except `rs/libmoq`, whose shutdown work is abandoned per [Generated C bindings](/quest/m1/c/README.md). IETF sessions still close at once until [IETF drain before close](/quest/m2/ietf-drain-before-close.md) lands. - -Public API: breaking on dev, `shutdown` becomes async, drains, and returns the close error in moq-ffi and every wrapper. Wire: no format change; a binding's shutdown now sends queued data and FIN before closing instead of discarding them. diff --git a/quest/m1/cache-expiry-growth.md b/quest/m1/cache-expiry-growth.md index 0aafd2b2c1..7bbe215a52 100644 --- a/quest/m1/cache-expiry-growth.md +++ b/quest/m1/cache-expiry-growth.md @@ -27,7 +27,3 @@ Otherwise reproduce with a focused test. Unexpired groups at higher throughput, expiry not running on some path, or groups held outside the pool's accounting would each explain it. Fix what is actually wrong. Consider whether an unbounded default is the right default for an origin at all. - -## Related - -- [Relay memory](/quest/m1/relay-memory.md) - what an announcement costs in memory diff --git a/quest/m1/cache-wall-eviction.md b/quest/m1/cache-wall-eviction.md index 083a2857e5..f00fe1a37c 100644 --- a/quest/m1/cache-wall-eviction.md +++ b/quest/m1/cache-wall-eviction.md @@ -42,4 +42,3 @@ Public API: none from the plan. Wire: none. ## Related - [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - relay memory past the expiry window, in the same cache -- [Cache shard](/quest/m1/perf/cache-shard.md) - the pool's shared counters under many workers diff --git a/quest/m1/capture-control.md b/quest/m1/capture-control.md deleted file mode 100644 index 958853788f..0000000000 --- a/quest/m1/capture-control.md +++ /dev/null @@ -1,50 +0,0 @@ -# [M] Capture Control: settled name, loud cut, prompt cancel, catalog clock - -## Goal - -The capture handles from [#4184](https://github.com/moq-dev/moq/pull/4184), -on `dev` only, get their final shape before release: - -- `encode::CaptureOptions` is `encode::Capture` in both moq-audio and - moq-video. -- `Control::cut()` on video tells the caller when the backend cannot force a - keyframe. Today the driver logs one warning on `CutUnsupported` and keeps - the GOP cadence, so a recording or resume boundary silently never appears. -- Dropping the last `Control` ends the driver promptly, including while the - startup probe, `capture::open`, `Sink::open`, or an encode is in flight. - Today those awaits never see the handle close, so a camera or permission - prompt can outlive its owner. -- Capture publishers stamp on the clock their catalog advertises, with no - separate clock to pass. Today both `CaptureOptions` carry their own - `clock: moq_mux::Clock`, and `Default` builds a fresh one, so a caller - relying on the default publishes against a mapping the catalog never - advertised. `moq import capture` passes `catalog.clock()`, the only correct - value. - -## Plan - -Decided: - -- Rename to `encode::Capture`, which reads as the capture half next to - `encode::Options`. Update moq-cli and the docs; moq-ffi and libmoq do not - call the capture paths, so no binding mirror exists today. -- `cut()` fails loud with an error rather than logging. The encoder is opened - lazily, so the handle may not know yet; the probe already opens one, which - is one place to learn it early. Choose between `cut()` returning - `Result` (refusing once the backend is known) and the driver ending with - `CutUnsupported`, and record the choice here. -- Race every await in the driver against the controls closing, rather than - only the idle wait, so the probe and the demand-driven opens both cancel. -- Drop the `clock` field from both options; `Control::new` already takes the - catalog producer, so it reads `catalog.clock()`. Update moq-cli and any - binding that forwards a clock. The clock fixtures in both crates already - pass the catalog's clock, so they keep grading the same path. This absorbs - the former m2 capture-clock-source quest: it breaks the same `dev` options, - so one break lands instead of two. - -This is a `dev` break layered on #4184; land it on `dev` before the release -that first publishes these handles. - -Tests: a backend without forced keyframes surfaces `CutUnsupported` to the -caller; dropping the last `Control` during a slow fake open or probe returns -from `Driver::run` without finishing the open. diff --git a/quest/m1/cli-serve.md b/quest/m1/cli-serve.md index 9456879ca9..613a54cff5 100644 --- a/quest/m1/cli-serve.md +++ b/quest/m1/cli-serve.md @@ -29,7 +29,3 @@ relay. `moq import --listen`; an unscoped one is refused when a key is configured; the unauthenticated import-to-export smoke keeps passing with `--auth-public`. - -## Required - -- [`moq relay`](/quest/m1/moq-relay-subcommand.md) - the CLI hosts the relay library, which `serve` comes from diff --git a/quest/m1/close-codes.md b/quest/m1/close-codes.md deleted file mode 100644 index 8478905c3b..0000000000 --- a/quest/m1/close-codes.md +++ /dev/null @@ -1,42 +0,0 @@ -# [M] Close codes on every transport - -## Goal - -A client sees the application close code its peer sent, for example -`SessionError::App(4011)` from `Session::abort` or `Request::reject`, over -WebSocket (qmux) and raw QUIC (`moqt://`), as it already does over -WebTransport. Never `Transport("connection closed")` or its own `Internal`. - -## Plan - -Both bugs are upstream; fix them at the source, release, and bump the pins. - -Targets `dev`: #4262 (open) does this, and the qmux release pulls in -`web-transport-trait` 0.5 (`SendStream::set_priority` takes `i32`), a Rust -API break. It reports `@moq/qmux` already keeps the first close. - -- qmux 0.5.1 (`moq-dev/web-transport`) lets later writes overwrite the - recorded close in `session.rs`: the WS Close frame read after - APPLICATION_CLOSE (reader loop, backend `send_replace`), a local `close()` - after the peer closed, and a second peer APPLICATION_CLOSE. `accept_uni` and - `accept_bi` also return a bare `Closed`. Make the first close win, make - `close()` a no-op once closed, and have `accept_*` return the recorded - reason. Add a qmux test where APPLICATION_CLOSE and EOF arrive together. -- `web-transport-moq` 1.3.2 (`moq-dev/noq#11`) maps a raw `moqt://` peer's - `ApplicationClosed` code directly. Done; only the regression below remains. -- moq-net's `close(Internal)` after a transport error is correct: closing a - closed connection does nothing. Do not work around it here. -- One moq-tokio regression runs the issue's three cases (abort after accept, - abort then drop, reject during handshake) over `https://`, `ws://`, and - `moqt://`, and fails on the current pins. -- Check whether `@moq/net`'s qmux peer keeps the first close too; fix it in - the same PR if not. - -## Closes - -- [#4249](https://github.com/moq-dev/moq/issues/4249) - application close code is lost over the WebSocket (qmux) transport - -## Related - -- [qmux on noq](/quest/m1/quic/qmux.md) - the rewrite must keep first-close-wins -- [io_uring close](/quest/m1/quic/uring-close.md) - the same symptom class on the io_uring backend diff --git a/quest/m1/cluster-routing.md b/quest/m1/cluster-routing/README.md similarity index 98% rename from quest/m1/cluster-routing.md rename to quest/m1/cluster-routing/README.md index d15a3d7725..0f3d74f78f 100644 --- a/quest/m1/cluster-routing.md +++ b/quest/m1/cluster-routing/README.md @@ -204,6 +204,4 @@ registry. What decides the wire: ## Related -- [Redundant ingest](/quest/m2/redundant-ingest.md) - builds on the `--hop` failover this must keep or replace -- [Routing cost domains](/quest/m2/routing-cost-domains.md) - cost across the cluster boundaries this keeps path vector - [Cross-relay delivery under bursts](/quest/m1/cross-relay-bursts.md) - its #4349 report also shows closed broadcasts announced for up to 229 s and flapping between Retracted and Announced across nodes, evidence for per-incarnation seqnos diff --git a/quest/m1/color-model.md b/quest/m1/color-model.md index cf39b1ed8b..ab20fc07fd 100644 --- a/quest/m1/color-model.md +++ b/quest/m1/color-model.md @@ -52,8 +52,3 @@ Test each source of truth in isolation, a source that signals nothing, a conflict between VUI and container resolving to the bitstream, a container box that fills a gap the bitstream left unspecified, and an SDR round trip that stays byte-identical. - -## Related - -- [SEI sidecars](/quest/m2/sei/README.md) - moves SEI out of the video track; - the display metadata inside it needs the home this quest builds diff --git a/quest/m1/cpp/README.md b/quest/m1/cpp/README.md index b20d4c9f7e..e46e976821 100644 --- a/quest/m1/cpp/README.md +++ b/quest/m1/cpp/README.md @@ -74,7 +74,4 @@ Confirmed in [#4100](https://github.com/moq-dev/moq/pull/4100): ## Related -- [C# through moq-ffi](/quest/m2/cs/README.md) - the same recipe with NordSecurity's C# generator -- [Unreal prototype](/quest/m2/unreal.md) - a UE5 module consumes the package with exceptions disabled - [vcpkg registry](/quest/m2/cpp-vcpkg.md) - a registry we own serves the prebuilt package to `vcpkg` manifests -- [Conan remote](/quest/m2/cpp-conan.md) - a remote we own serves the same tarball to `conan install` diff --git a/quest/m1/cpp/generator.md b/quest/m1/cpp/generator.md index 1a3c917d0d..66dc7d8be8 100644 --- a/quest/m1/cpp/generator.md +++ b/quest/m1/cpp/generator.md @@ -44,4 +44,4 @@ stop here and write down why. ## Related -- [C# generator](/quest/m2/cs/generator.md) - the same 0.32 port against NordSecurity's C# generator +- [C# generator](/quest/m3/cs/generator.md) - the same 0.32 port against NordSecurity's C# generator diff --git a/quest/m1/cross-relay-bursts.md b/quest/m1/cross-relay-bursts.md index a9415011e8..14e636a3f9 100644 --- a/quest/m1/cross-relay-bursts.md +++ b/quest/m1/cross-relay-bursts.md @@ -24,7 +24,3 @@ cluster-routing evidence, not this quest's scope. ## Closes - [#4349](https://github.com/moq-dev/moq/issues/4349) - close this issue when the quest finishes - -## Related - -- [Cluster routing](/quest/m1/cluster-routing.md) - owns the stale and flapping announcements from the same report diff --git a/quest/m1/data-track-clock.md b/quest/m1/data-track-clock.md index 170d34a09a..f9e1b0cd35 100644 --- a/quest/m1/data-track-clock.md +++ b/quest/m1/data-track-clock.md @@ -13,7 +13,3 @@ maps its timestamps to a different clock than the media. Have data tracks read the catalog's clock when they stamp, not a copy made when they were created. Keep it crate-private if possible. Test: a data track created before an importer's first frame stamps on the anchored clock. - -## Required - -- [Remove live()](/quest/m1/remove-live.md) - introduces the first-frame clock anchor diff --git a/quest/m1/drain/README.md b/quest/m1/drain/README.md index 786b65f106..6d527e3740 100644 --- a/quest/m1/drain/README.md +++ b/quest/m1/drain/README.md @@ -55,7 +55,3 @@ by the stop deadline and encoder reconnect. migrates on GOAWAY with a handover and the guarded redirect the Rust client already has, and the Rust drain path gets its regression test - [JS GOAWAY requests](/quest/m1/drain/js-goaway-requests.md) - after GOAWAY the JS client opens no new request on the old session, like Rust - -## Related - -- [Cluster routing](/quest/m1/cluster-routing.md) - the configured topology and link costs a second relay per PoP joins diff --git a/quest/m1/effect-cancel.md b/quest/m1/effect-cancel.md deleted file mode 100644 index 540fa9db1a..0000000000 --- a/quest/m1/effect-cancel.md +++ /dev/null @@ -1,12 +0,0 @@ -# [XS] Remove effect.cancel - -## Goal - -`@moq/signals` no longer exports `Effect.cancel`. Its only use was racing a -per-run pending promise, which leaks a reaction per call. It is already -marked `@internal`, and every internal caller uses `effect.race` instead. - -## Plan - -A published break to `@moq/signals`, so it targets dev. Delete the getter and -the promise backing it, and fix any caller or doc the deprecation left behind. diff --git a/quest/m1/ffi-shape/README.md b/quest/m1/ffi-shape/README.md index 878b592172..f1edaccb15 100644 --- a/quest/m1/ffi-shape/README.md +++ b/quest/m1/ffi-shape/README.md @@ -56,7 +56,3 @@ work no child does: - [Net](/quest/m1/ffi-shape/net.md) - client and server take config records, snapshots are records, and the verbs match moq-net - [Media](/quest/m1/ffi-shape/media.md) - catalog, import, and container consume move under `media` - [Codecs](/quest/m1/ffi-shape/codec.md) - audio and video encoders and decoders move under their own namespaces with one constructor shape - -## Related - -- [Track demand](/quest/m1/track-demand.md) - the same `demand()` cleanup in Rust and JS diff --git a/quest/m1/ffi-shape/codec.md b/quest/m1/ffi-shape/codec.md index a5668cfc19..629f7b9626 100644 --- a/quest/m1/ffi-shape/codec.md +++ b/quest/m1/ffi-shape/codec.md @@ -22,7 +22,7 @@ The video encoder's output mirrors moq-video's `encode::Gop`: `Keyframe { interval }` variant, defaulting to keyframes at two seconds, and documented as non-exhaustive like the core. The wrappers expose it as an enum their callers construct, not one they are asked to match, so -[intra-refresh bindings](/quest/m2/intra-refresh/bindings.md) adds the refresh +[intra-refresh bindings](/quest/m3/intra-refresh-bindings.md) adds the refresh variant additively instead of breaking `gop` a second time. Go gets no uniffi default, so its zero value must read as keyframe mode. diff --git a/quest/m1/frame-slot-charge.md b/quest/m1/frame-slot-charge.md index d7c199e5aa..cb93048fb9 100644 --- a/quest/m1/frame-slot-charge.md +++ b/quest/m1/frame-slot-charge.md @@ -47,7 +47,3 @@ Found by CodeRabbit on #3523, which fixed the one-frame-per-group undercount that was OOM-killing relays serving chat, and deliberately left out of it. Public API: none, unless `MAX_CACHE_BYTES` is restated. Wire: none. - -## Related - -- [Relay memory](/quest/m1/relay-memory.md) - the per-announcement half of the same question, whose figures also predate the current accounting diff --git a/quest/m1/gpu-ci.md b/quest/m1/gpu-ci.md index 64bacaeeec..e294915c56 100644 --- a/quest/m1/gpu-ci.md +++ b/quest/m1/gpu-ci.md @@ -58,4 +58,3 @@ Public API: none. Wire: none. ## Related - [Video hardware validation](/quest/m3/video-hardware.md) - hardware paths nothing runs yet -- [Runtime QA hosts](/quest/m2/runtime-qa-hosts.md) - on-demand jobs on hardware hosts, a broader contract than a nightly diff --git a/quest/m1/hls-discontinuity-sequence.md b/quest/m1/hls-discontinuity-sequence.md deleted file mode 100644 index a191a9e2aa..0000000000 --- a/quest/m1/hls-discontinuity-sequence.md +++ /dev/null @@ -1,37 +0,0 @@ -# [S] moq-hls segments carry the absolute discontinuity sequence - -## Goal - -Every `moq_hls::export::Segment` reports the discontinuity sequence it belongs -to, the value a recorder writes as `EXT-X-DISCONTINUITY-SEQUENCE`, so two -cursors on sibling renditions agree no matter when each was created. On `dev`, -`Segment::discontinuity` is a `u64` count of breaks since the cursor's -previous segment ([#4068](https://github.com/moq-dev/moq/pull/4068)). A cursor -created after its rendition rebinds starts its count from its own first row, -so it disagrees with a sibling on the baseline. - -## Plan - -Decided: - -- Report the absolute sequence the timeline fanout already stamps on each - row, instead of the difference between rows. A recorder writes - `EXT-X-DISCONTINUITY-SEQUENCE` from the first segment and an - `EXT-X-DISCONTINUITY` wherever the value changes. -- Land it on `dev` before the next moq-hls release, so it ships in the same - breaking release as the existing `bool` to `u64` change rather than - breaking the field twice. - -Guidance: - -- `Position` in `export/segments.rs` then only tracks `after`; the - skip/emit baseline logic goes away. Check the serve path's playlist - rendering (`rendition.rs`, `playlist.rs`) already derives its tags from the - same stamp. -- The sequence is absolute within one `Broadcaster`. Document what a recorder - should do when its broadcaster is rebuilt and the sequence restarts. -- Update the field doc and the tests that assert per-cursor counts; add one - where a cursor created after a rebind reports the same sequence as a cursor - that has run since the start. -- moq.pro's recorder and index store the count today; note the change for its - pin bump. diff --git a/quest/m1/hop-aligned-import.md b/quest/m1/hop-aligned-import.md index 6f6f68b6c6..065938daa0 100644 --- a/quest/m1/hop-aligned-import.md +++ b/quest/m1/hop-aligned-import.md @@ -40,6 +40,5 @@ survives the standby joining and the incumbent stopping. ## Related -- [Redundant ingest](/quest/m2/redundant-ingest.md) - splicing across first hops and two encoders, which this does not attempt - [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - the TS re-anchor shift that must stay input-derived - [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - a redundant pair shares one epoch diff --git a/quest/m2/ietf-drain-before-close.md b/quest/m1/ietf-drain-before-close.md similarity index 84% rename from quest/m2/ietf-drain-before-close.md rename to quest/m1/ietf-drain-before-close.md index e5c034130d..732dd31a91 100644 --- a/quest/m2/ietf-drain-before-close.md +++ b/quest/m1/ietf-drain-before-close.md @@ -12,7 +12,3 @@ the shared one second `CLOSE_TIMEOUT`. its sessions close at once. Count the IETF publisher's in-flight subscribe and fetch serves the way `lite::Publisher::drained` does, and report them there. Extend `rs/moq-net/tests/session_close.rs` to cover an IETF version. - -## Related - -- [Session close](/quest/m1/session-close.md) - the graceful end that withdraws announces diff --git a/quest/m1/js-closed-track-leak.md b/quest/m1/js-closed-track-leak.md deleted file mode 100644 index bc1755302c..0000000000 --- a/quest/m1/js-closed-track-leak.md +++ /dev/null @@ -1,18 +0,0 @@ -# [XS] A late subscriber to a closed JS track is released - -## Goal - -On `dev`, where `@moq/net`'s track retention can be unlimited, a subscriber -that joins a track after its producer closed is removed from the track cache -once it's done, instead of being held forever. - -## Plan - -Found while landing the main-into-dev sync (#4428). That PR stopped caching -a subscriber the producer closes when retention is unlimited, since nothing -ages it out, but a subscriber that arrives after the close takes a different -path in `js/net/src/track.ts` and is never cleaned up. Release it on the same -terms, and add a test that a late subscriber to a closed track leaves no -cache entry once it drops. - -Public API: none. Wire: none. Targets `dev`, where unlimited retention lives. diff --git a/quest/m1/js-fetch.md b/quest/m1/js-fetch.md index a02da35c4c..bd58adf0c6 100644 --- a/quest/m1/js-fetch.md +++ b/quest/m1/js-fetch.md @@ -40,7 +40,3 @@ A published API break, if the chosen shape requires one, goes through dev. ## Required - [Dynamic track identity](/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md) - settles shared producer identity before adding on-demand group requests - -## Related - -- [Browser archive](/quest/m1/archive/browser.md) - supplies memory or OPFS archive data through this generic request surface diff --git a/quest/m2/mobile-ownership.md b/quest/m1/mobile-ownership.md similarity index 100% rename from quest/m2/mobile-ownership.md rename to quest/m1/mobile-ownership.md diff --git a/quest/m1/moxygen/fetch.md b/quest/m1/moxygen/fetch.md index e38d1e533b..c62dd2289b 100644 --- a/quest/m1/moxygen/fetch.md +++ b/quest/m1/moxygen/fetch.md @@ -21,10 +21,6 @@ cannot express is still an explicit refusal, not a hang. The moxygen FETCH cases that ask for whole groups are the check. The rest of that suite is not. -## Required - -- [Legal IETF input](/quest/m0/ietf-legal-input.md) - decodes the draft-20+ FETCH layout this serves - ## Related - [Moxygen compatibility](/quest/m1/moxygen/README.md) - the line this belongs to diff --git a/quest/m1/obs-moq-video/README.md b/quest/m1/obs-moq-video/README.md index 1e41ad84c8..e0712e8ac4 100644 --- a/quest/m1/obs-moq-video/README.md +++ b/quest/m1/obs-moq-video/README.md @@ -20,22 +20,16 @@ The quests separate portable decoding, platform GPU delivery, audio, and publish - [Video source replacement](/quest/m1/obs-moq-video/source.md) - remove the FFmpeg video decode and attempt macOS GPU delivery immediately, with a working CPU fallback on other platforms - [Audio playback](/quest/m1/obs-moq-video/audio-playback.md) - replace the FFmpeg audio decode with moq-audio -- [Windows decoded frames](/quest/m1/obs-moq-video/decode-windows.md) - present decoded D3D11 surfaces in OBS without CPU readback -- [Linux decoded frames](/quest/m1/obs-moq-video/decode-linux.md) - present supported native decoded surfaces with visible CPU fallback - [Linux bundle](/quest/m1/obs-moq-video/linux-bundle.md) - attach a portable Linux x86_64 tarball to every obs-moq release once FFmpeg is gone - [Encoder presets](/quest/m1/obs-moq-video/presets.md) - define and measure shared low-latency, balanced, and quality policies - [Preset parity](/quest/m1/obs-moq-video/preset-parity.md) - audio stores and reports its preset like video, defaults to Balanced, and the preset claims hold - [Audio publishing](/quest/m1/obs-moq-video/audio-publish.md) - back an internal OBS Opus encoder with moq-audio - [Video publishing](/quest/m1/obs-moq-video/adapter.md) - back an internal OBS video encoder with moq-video and expose the combined opt-in mode - [Rate control](/quest/m1/obs-moq-video/rate-control.md) - the plugin reserves its bitrate and retunes the OBS encoder to the grant -- [macOS GPU input](/quest/m1/obs-moq-video/macos.md) - feed the encoder from the OBS compositor without CPU readback -- [Windows GPU input](/quest/m1/obs-moq-video/windows.md) - import or blit OBS D3D11 textures with explicit synchronization - [VP8/VP9 decoding](/quest/m1/obs-moq-video/vpx.md) - restore those playback codecs without an FFmpeg ABI dependency ## Related - [OBS migration](/quest/m1/cpp/obs.md) - every quest here starts from the plugin on the generated C++, so codec surface is designed in moq-ffi and reaches libmoq and the other wrappers through the Cross-Package Sync table, not as OBS-only C symbols -- [Linux GPU input](/quest/m2/obs-linux-gpu.md) - allocation-export feasibility and its dependent implementation are deferred -- [VAAPI encode and decode](/quest/m4/video-vaapi.md) - owns Linux backend decode/import capabilities; reconcile its older dependency assumptions against current code - [Video hardware validation](/quest/m3/video-hardware.md) - physical hardware evidence is required for each claimed GPU path - [Audio codecs](/quest/m1/audio-codecs/README.md) - HE-AAC, multichannel, and native AAC encode reach the OBS source and encoder adapters through moq-ffi diff --git a/quest/m1/obs-moq-video/linux-bundle.md b/quest/m1/obs-moq-video/linux-bundle.md index 8a10983f3a..a72503ca7c 100644 --- a/quest/m1/obs-moq-video/linux-bundle.md +++ b/quest/m1/obs-moq-video/linux-bundle.md @@ -16,7 +16,3 @@ Every `obs-moq-v*` release attaches a Linux x86_64 tarball that loads into a sto - [Video source replacement](/quest/m1/obs-moq-video/source.md) - removes the FFmpeg video linkage that makes a Linux binary non-portable - [Audio playback](/quest/m1/obs-moq-video/audio-playback.md) - removes the FFmpeg audio linkage - -## Related - -- [Linux decoded frames](/quest/m1/obs-moq-video/decode-linux.md) - native surface delivery lands on top of the portable CPU path this bundle ships diff --git a/quest/m1/obs-moq-video/preset-parity.md b/quest/m1/obs-moq-video/preset-parity.md index d2c2cd8623..42a87504b3 100644 --- a/quest/m1/obs-moq-video/preset-parity.md +++ b/quest/m1/obs-moq-video/preset-parity.md @@ -40,8 +40,8 @@ The shape is settled; this finishes it: - Known gap: VAAPI reports `LowLatency` whatever was asked, and V4L2 and MediaCodec report unconfirmed; no quest owns measuring and mapping presets for them. Media Foundation and VideoToolbox are owned by - [Windows GPU input](/quest/m1/obs-moq-video/windows.md) and - [macOS GPU input](/quest/m1/obs-moq-video/macos.md). + [Windows GPU input](/quest/m2/obs-windows.md) and + [macOS GPU input](/quest/m2/obs-macos.md). Public API: additive on moq-audio (the stored preset and its `Applied` report); the unpublished audio `Preset` default changes. Wire: none. diff --git a/quest/m1/path-patterns.md b/quest/m1/path-patterns.md deleted file mode 100644 index ce5536ce77..0000000000 --- a/quest/m1/path-patterns.md +++ /dev/null @@ -1,105 +0,0 @@ -# [M] Path patterns - -## Goal - -Every predicate over a MoQ broadcast path uses one matcher. Tokens, -origin scopes, announce interests, and public access rules can express -`pid/*/chat` and `**/*.hang` without maintaining competing glob dialects. -Routing is not a predicate here: advertisements stay prefixes, and -[announcement shapes](/quest/m3/announce-shapes.md) owns non-prefix routing. - -Literal paths remain coordinates, not sets. Roots, joins, exact broadcast -names, URL paths, filesystem paths, and object-store keys keep their own -types. The relay's `/announced/*prefix` debug endpoint remains a prefix-only -exception. - -## Plan - -### Dialect - -A v1 pattern is canonical `/`-separated segments: - -- a literal; -- `*`, matching one complete segment; -- `lit*lit`, with one `*` matching bytes inside one segment (`*.hang`, `foo*`, - `foo.*.hang`); -- `**`, matching zero or more complete segments, at most once per pattern. - -Patterns are exact by default. `foo` matches only `foo`, `foo/**` matches its -subtree including `foo`, `**` matches every path, and the empty pattern matches -only the current root. Reject leading, trailing, or repeated `/`, more than one -`*` in a segment, `**` mixed with literal bytes, and more than one `**`. A -second star in a segment stays reserved: matching it is still linear, but -containment stops being two string compares. Literal `*` needs no escape: new -path construction and publication reject it, while decoders tolerate it on -legacy protocol versions during rollout. - -Construction moves `**` before adjacent `*` segments: `*/**` becomes -`**/*`. Equivalent wildcard placements therefore share one text and identity. - -A pattern list is an unordered union reduced by containment. The shared -algebra supplies matching, overlap, containment, a literal head, and exact -set-valued rebasing. The set-valued result is load-bearing: rebasing `**/a` at -`a` must preserve both the root match and deeper paths ending in `a`. Union -containment is per member: a candidate covered only jointly by several members -(`a/**` against `a`, `a/*`, `a/*/**`) is refused, so the check stays linear and -a grant that means a subtree writes `a/**`. Pattern precedence uses one total -structural specificity everywhere rules overlap, ordered by literal segments, -then no `**`, then `lit*lit` segments, then `*` segments, then literal bytes -pinned inside `lit*lit` segments, then literal head length. That order agrees -with containment (a strict superset always ranks lower); equal patterns form -the same tier. - -### Ownership and compatibility - -`moq-pattern` and `@moq/pattern` own the grammar and algebra; `moq-net`, -`moq-auth`, `@moq/net`, and `@moq/auth` re-export them. Literal `Path` types -stay in `moq-net` / `@moq/net`. Golden cross-language vectors -(`rs/moq-pattern/tests/pattern.json`), exhaustive small cases, randomized round -trips, and the moq-net fuzz harness's `pattern` target prevent semantic drift at -the authorization boundary. Matching is linear and inherits `Path::MAX_PARTS` -(32), which also bounds residual expansion. - -Grants and claims carry no version. `moq-auth` grants are patterns: `foo` -means exactly `foo` and a subtree is `foo/**`. The published `put`/`get` -prefix encoding stays readable as subtrees, and subtree-only grants are still -written in it so older verifiers keep working. A wire message that carried -prefixes keeps them on the protocol versions that defined them; only new -versions carry patterns. - -The syntax follows Ant-style path patterns without `?`, classes, or braces. -NATS subjects motivate segment wildcards and reserved wildcard bytes; Vault -ACLs motivate structural specificity. Common Access -Token and `draft-ietf-moq-c4m-01` provide exact, prefix, and suffix matches per -namespace field, including exact depth with a trailing `nil`. Document the -exact common subset and keep the richer MoQ forms explicit rather than claiming -CAT cannot represent `pid/*/chat`. - -### Grants on the wire - -AUTH grants on moq-lite-06 carry the shared pattern semantics, without -changing older protocol versions. Interest stays a prefix: [#3770](https://github.com/moq-dev/moq/pull/3770) -keeps patterns off the announce wire, so ANNOUNCE_REQUEST and -SUBSCRIBE_NAMESPACE carry the prefix the caller asked for and a wildcard is -an optional filter on the consume side. [Announcement shapes](/quest/m3/announce-shapes.md) -later adds only exact, suffix, and prefix+suffix to moq-lite's ANNOUNCE_REQUEST; -any other pattern stays a consume-side filter. - -Replace lite-06 AUTH grant prefixes with patterns in Rust and JavaScript in -the same change. Update the lite draft and version-gated fixtures together. -Authorize by exact containment in the subscriber's v1 grant. Older moq-lite -versions keep their existing prefix wire and behavior; a grant they cannot -represent is refused, not narrowed. Cluster peers adopt nothing as a side -effect of this wire work. - -Test Rust and JavaScript interop, leading wildcards, `**` zero-segment -matches, containment refusal, and old-version behavior. - -## Required - -- [Lite auth](/quest/m1/auth/lite.md) - establish the AUTH exchange before upgrading its grants to patterns - -## Related - -- [Wildcard advertisements](/quest/m0/wildcard/README.md) - routes on prefix - claims; the matcher only filters them against consume-side interest diff --git a/quest/m1/perf/3203-moq-uring-add-opt-in-napi-busy-polling.md b/quest/m1/perf/3203-moq-uring-add-opt-in-napi-busy-polling.md deleted file mode 100644 index 5cce493345..0000000000 --- a/quest/m1/perf/3203-moq-uring-add-opt-in-napi-busy-polling.md +++ /dev/null @@ -1,30 +0,0 @@ -# [M] moq-uring: add opt-in NAPI busy polling - -## Goal - -A worker can opt into io_uring NAPI busy polling from relay configuration, -off by default, reported honestly when unsupported, with bare-metal latency -and idle-cost numbers recorded before it is recommended anywhere. - -## Plan - -Follow-up to #2875. - -The relay already pins one worker and one ring per core. io_uring NAPI busy polling can keep a worker close to the NIC receive path and reduce wakeup latency, at the cost of continuously consuming CPU and power while it polls. - -#### Proposal - -- Add an explicit worker-level NAPI configuration with disabled as the default. -- Start with dynamic NAPI-ID tracking through `register_napi`. Add static IDs only if deployment can discover and maintain the correct queue IDs reliably. -- Make busy-poll duration and preferred-busy-poll behavior observable in relay configuration and stats. -- Report unsupported registration clearly. Do not silently claim the mode is active. -- Ensure unregister and worker teardown are safe. -- Document that this mode is for dedicated, latency-sensitive cores, not general self-hosting. - -#### Acceptance - -Measure on bare metal with the deployment NIC and queue affinity configured, not loopback. Compare disabled and several busy-poll durations under low, medium, and saturated load. Record p50, p99, and p999 packet latency, relay CPU, CPU idle residency, interrupts, drops, goodput, and power if available. Ship only as opt-in unless fleet-level data shows an acceptable idle-cost tradeoff. - -## Closes - -- [#3203](https://github.com/moq-dev/moq/issues/3203) - close this issue when the quest finishes diff --git a/quest/m1/perf/3205-moq-uring-register-reusable-io-uring-enter-wait-arguments.md b/quest/m1/perf/3205-moq-uring-register-reusable-io-uring-enter-wait-arguments.md deleted file mode 100644 index 904e0547ed..0000000000 --- a/quest/m1/perf/3205-moq-uring-register-reusable-io-uring-enter-wait-arguments.md +++ /dev/null @@ -1,35 +0,0 @@ -# [M] moq-uring: register reusable io_uring_enter wait arguments - -## Goal - -Timed parking reuses one registered `io_uring_reg_wait` entry per worker via -`IORING_ENTER_EXT_ARG_REG` on Linux 6.13+, keeping today's `SubmitArgs` -path (rs/moq-uring/src/worker.rs:305, :410) as the 6.12 fallback, retained -only if cycles per enter measurably drop. - -## Plan - -Follow-up to #2875 and the CQ batching experiment in #3200. - -Timed parking currently builds a `Timespec` and `SubmitArgs` for each wait, then asks the kernel to copy the extended enter arguments. Linux 6.13 added registered wait regions and `IORING_ENTER_EXT_ARG_REG`, allowing a ring to reuse kernel-known wait storage. - -#### Proposal - -- After #3200 fixes the wait shape, register one stable `io_uring_reg_wait` entry per worker. -- Update its absolute deadline, batch target, and minimum wait before each enter, then use `IORING_ENTER_EXT_ARG_REG`. -- Keep the current extended-argument path as the Linux 6.12 fallback. Do not raise the kernel floor for this micro-optimization without benchmark evidence. -- Prefer safe upstream support in the `io-uring` crate. If the current release lacks the registration API, contribute or upgrade that support instead of spreading raw ABI calls through the worker. -- Keep registration ownership and unregistration in one worker-owned type. -- Test repeated deadline changes, no-deadline waits, interrupted enters, and teardown. - -#### Acceptance - -Measure this after the winning #3200 configuration, where enter frequency and argument shape are known. Record cycles and instructions per enter, enters per second, relay CPU, and latency. Retain the 6.13 fast path only if the end-to-end CPU change is measurable. - -## Required - -- [#3200: moq-uring: batch completion wakeups with MIN_TIMEOUT](/quest/m1/perf/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md) - complete the prerequisite issue first - -## Closes - -- [#3205](https://github.com/moq-dev/moq/issues/3205) - close this issue when the quest finishes diff --git a/quest/m1/perf/README.md b/quest/m1/perf/README.md index 95ae136cdd..b8610dfade 100644 --- a/quest/m1/perf/README.md +++ b/quest/m1/perf/README.md @@ -45,30 +45,11 @@ row per io_uring worker. ## Required -- [Open contract](/quest/m1/perf/uring-open-contract.md) - plan concurrent WebTransport opening and cancellation - - [Announce replay](/quest/m1/perf/announce-replay.md) - the initial announce set replays in linear time, so joins don't slow with the route count - [Group cost](/quest/m1/perf/group-cost.md) - count and cut the allocations and time spent relaying one small group to one viewer - [One enter per turn](/quest/m1/perf/uring-one-enter.md) - a parking turn pays one io_uring_enter, submits flush deferred completions, and SQEs per enter is a counter - [Run to quiescence](/quest/m1/perf/uring-quiescence.md) - a received packet's reply is staged in the same turn, under a pass budget that keeps the fairness rule - [Lock wait](/quest/m1/perf/lock-wait.md) - each worker reports time blocked on cross-worker locks, deciding whether the shared model needs work - [Ingest batch](/quest/m1/perf/ingest-batch.md) - relay ingest pays one lock, wake, and clock read per chunk burst instead of per chunk -- [Egress cache refresh](/quest/m1/perf/egress-keepalive.md) - measure refresh costs while preserving slow-reader retention -- [Owned decoding copies](/quest/m1/perf/coding-decode.md) - measure and reduce owned decode allocations and copies - [#3122](/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md) - moq-uring: ~2.5% of relay CPU is vdso clock reads; the drive loop and its callers each re-read Instant::now() -- [Cache shard](/quest/m1/perf/cache-shard.md) - stop hammering one process-global cache line from every worker - [#3199](/quest/m1/perf/3199-moq-uring-remove-sq-indirection-and-per-enter-ring-fd.md) - moq-uring: remove SQ indirection and per-enter ring fd lookup -- [#3200](/quest/m1/perf/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md) - moq-uring: batch completion wakeups with MIN_TIMEOUT -- [#3129](/quest/m1/perf/3129-moq-uring-write-the-webtransport-stream-header-at-open.md) - moq-uring: write the WebTransport stream header at open time, so finish() never owes one -- [Egress requeue](/quest/m1/perf/egress-requeue.md) - trains per turn on the egress driver becomes a measured budget instead of a hardcoded one -- [#3201](/quest/m1/perf/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - moq-uring: use SENDMSG_ZC for large UDP GSO trains -- [#3202](/quest/m1/perf/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md) - moq-uring: use fixed-file slots for worker UDP sockets -- [#3204](/quest/m1/perf/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md) - moq-uring: register TX-pool buffers for zero-copy sends -- [Priority set_track wakes](/quest/m1/perf/priority-set-track-wakes.md) - a track priority change stops waking groups that end up where they started -- [#3203](/quest/m1/perf/3203-moq-uring-add-opt-in-napi-busy-polling.md) - moq-uring: add opt-in NAPI busy polling -- [#3205](/quest/m1/perf/3205-moq-uring-register-reusable-io-uring-enter-wait-arguments.md) - moq-uring: register reusable io_uring_enter wait arguments - -## Related - -- [Send buffer pools](/quest/m2/quic-buffer-pool.md) - the stream-send - allocation question, measured on the same shapes diff --git a/quest/m1/perf/coding-decode.md b/quest/m1/perf/coding-decode.md deleted file mode 100644 index 1e9e7af930..0000000000 --- a/quest/m1/perf/coding-decode.md +++ /dev/null @@ -1,31 +0,0 @@ -# [S] Reduce owned path and byte-string decode copies - -## Goal - -Reduce measured allocation and payload-copy costs for owned byte strings, -strings, and paths without changing decoded values, errors, or wire bytes. - -## Plan - -`rs/moq-net/src/coding/decode.rs` decodes `Vec` through -`Buf::copy_to_bytes` followed by `to_vec`; `String` consumes that vector. -The first operation can return a shared view for a contiguous `Bytes` input, -but may allocate and copy for another `Buf`. Do not count every call as a -payload copy or assume a twofold improvement on production readers. - -Benchmark the actual reader input types, contiguous and chained buffers, -lengths 8 B through 1 KiB, and representative announcement messages. Compare -safe copying into an initialized vector with the current implementation. -Preserve bounds checks before allocation and UTF-8 validation. Keep owned -return types; borrowed decoding and new public APIs are outside this quest. - -Register a Criterion target in `moq-net` for discovery by `just bench`. -Report allocations, copied bytes, and throughput with paired base/current -runs. Retain the current implementation if no useful win is measured. -Add normal-CI regressions for fragmented input, truncated lengths/payloads, -invalid UTF-8, empty values, and path normalization; run the existing net -fuzz targets with `just rs fuzz path` and commit any regression inputs. - -## Related - -- [Benchmark comparisons](/quest/m1/performance-comparisons.md) - measurement conventions diff --git a/quest/m1/perf/egress-keepalive.md b/quest/m1/perf/egress-keepalive.md deleted file mode 100644 index 208b9efa9c..0000000000 --- a/quest/m1/perf/egress-keepalive.md +++ /dev/null @@ -1,35 +0,0 @@ -# [S] Measure and reduce egress cache-refresh overhead - -## Goal - -Reduce measured cache-refresh overhead during batched delivery without -expiring a group while a slow subscriber or FETCH reader is draining it. -Retain the current behavior if the improvement is within measurement noise. - -## Plan - -`group::Consumer::keep_alive` takes a state read guard and calls -`Charge::refresh`. Both lite and IETF publishers call it between completed -frame writes. This protects a drain that spans longer than `latency_max`; -a stamp only at batch fill is insufficient. `Charge::touch` already skips -population accounting when the coarse timestamp has not advanced. - -Measure read-guard, clock, and atomic costs separately for fast fanout and -flow-controlled readers, including SUBSCRIBE and FETCH. Preserve per-frame -liveness refresh unless an alternative proves the same retention behavior. -Do not introduce another clock or pool-accounting mechanism: those belong to -[Cache shard](/quest/m1/perf/cache-shard.md). Recheck its implementation -before optimizing the remaining publisher overhead. - -Extend the existing group and track Criterion targets and add a bounded -session regression to normal CI. Keep -`slow_prefetch_reader_survives_expiry` (`rs/moq-net/src/model/track.rs`), and cover expiry scans -while a batch drains, cancellation, and eventual expiry after reads stop. -Report delivered bytes, refresh cost, CPU, and throughput for paired runs; -fewer refresh calls alone are not evidence of a win. - -## Related - -- [Cache shard](/quest/m1/perf/cache-shard.md) - owns shared accounting and clock costs -- [Ingest batch](/quest/m1/perf/ingest-batch.md) - ingest batching -- [#3122](/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md) - runtime clock costs diff --git a/quest/m1/perf/egress-requeue.md b/quest/m1/perf/egress-requeue.md deleted file mode 100644 index 90ba398717..0000000000 --- a/quest/m1/perf/egress-requeue.md +++ /dev/null @@ -1,31 +0,0 @@ -# [XS] Trains per turn on the egress driver becomes a measured budget - -## Goal - -The io_uring QUIC driver stages one GSO train per turn and then wakes -itself (`Driver::flush`, rs/moq-uring/src/quic/noq/connection.rs:744), so a -deep backlog on one connection pays a whole driver turn per train of -`TRAIN_SEGMENTS = 63` segments (noq/connection.rs:23; `MAX_GSO_SEGMENTS = -64` at udp.rs:56 is the kernel cap). That cadence is a hardcoded fairness choice. -Make it a measured budget. - -## Plan - -The re-walk half of #3120 landed in #3134 (e6962b20e) on the since-deleted -quiche driver; the noq driver drains an event queue instead of walking -iterators (noq/connection.rs:697) and never had that shape. What is left is -the budget. - -- Add a trains-per-turn budget to `flush`. One train then - requeue is deliberate fairness across connections sharing a socket; keep - fairness by bounding the budget, and sweep 1, 2, and 4 trains per turn - under the fanout and single-heavy-connection shapes to see whether the - extra turn latency is real. -- Re-profile on noq; the numbers in #3120 are the quiche driver's. - -Acceptance: CPU per Gbps and throughput ceiling via `just bench BASE` on -Linux. Latency must not regress at the chosen budget. A no-win keeps 1. - -The [quiescence quest](/quest/m1/perf/uring-quiescence.md) sweeps this -budget together with its pass budget; land whichever runs first and fold the -other's sweep in. diff --git a/quest/m1/perf/group-cost.md b/quest/m1/perf/group-cost.md index a716f8889e..e3679232df 100644 --- a/quest/m1/perf/group-cost.md +++ b/quest/m1/perf/group-cost.md @@ -24,7 +24,3 @@ keeps that waiter and skips re-registering on lists that still hold it (2026-09: 228 to 122 allocations per viewer-group, 352 to 118 paced). Find the next largest source from there. A measured no-win abandons the quest, per this line's rules. - -## Related - -- [Owned decoding copies](/quest/m1/perf/coding-decode.md) - decode-side copies are part of the same per-group cost diff --git a/quest/m1/perf/lock-wait.md b/quest/m1/perf/lock-wait.md index 2fb63d7918..149134388e 100644 --- a/quest/m1/perf/lock-wait.md +++ b/quest/m1/perf/lock-wait.md @@ -26,8 +26,3 @@ fix in the shared model rather than the runtime; this quest only measures. Below one percent, record it and close the quest. Above, open a quest with the measured hot locks named, and only then decide between submitting staged SQEs before a blocking acquire and shrinking the lock in the model. - -## Related - -- [Cache shard](/quest/m1/perf/cache-shard.md) - one of the shared cells the - wait would point at diff --git a/quest/m1/perf/priority-set-track-wakes.md b/quest/m1/perf/priority-set-track-wakes.md deleted file mode 100644 index b830630139..0000000000 --- a/quest/m1/perf/priority-set-track-wakes.md +++ /dev/null @@ -1,42 +0,0 @@ -# [S] Stop set_track waking groups that end up where they started - -## Goal - -`lite::priority`'s `set_track` (rs/moq-net/src/lite/priority.rs:342) re-ranks -an item as `extract` (:322) then `place` (:261). The extract shifts every following vec entry up one and the place shifts them back -down, so an entry whose rank does not net-change is still woken: the shift up -takes its parked waker, and the shift back finds nothing left to restore. - -A track priority change should wake only the groups whose rank actually moved. - -## Plan - -The insert and remove paths are already exact, because each performs a single -monotone shift where every touched entry genuinely moves by one. Only the -double mutation in `set_track` can cancel itself out. - -Two shapes, cheapest first: - -- Defer the reconcile. Have `update_location` (:236) record the touched id and - leave `PriorityEntry::rank` (:185) holding the last *published* rank, then compare once at - the end of the mutation and wake only the net movers. Simple and total, but it - adds a second slab lookup per shifted entry to the insert hot path, so measure - it against `priority_queue_insert_front` (rs/moq-net/benches/priority.rs:25) - before taking it. -- Rotate instead of remove-and-reinsert. Moving an item within the sorted vec - only shifts the entries strictly between its old and new index, so computing - that range directly is both exact and less work than the two full shifts. It - has to keep the vec/overflow boundary in `place` intact, including the case - where the re-ranked item crosses it. - -The queue rework in #3298 left the wake count on this path as it was. It is a -cold path (a SUBSCRIBE_UPDATE -priority change), never group open or close. - -Acceptance: a test that parks each handle on its own waiter, calls `set_track` -on the front entry within a range where it stays first, and asserts no other -handle woke. No regression on the `priority_queue_insert_front` benches. - -## Related - -- [Hierarchical scheduling](/quest/m1/quic/scheduler.md) - removes this queue where the new scheduler replaces it diff --git a/quest/m1/perf/uring-one-enter.md b/quest/m1/perf/uring-one-enter.md index 5ff2baf3de..d3c3a8ea64 100644 --- a/quest/m1/perf/uring-one-enter.md +++ b/quest/m1/perf/uring-one-enter.md @@ -42,7 +42,5 @@ fails without the flag. Latency must not regress. ## Related -- [#3200](/quest/m1/perf/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md) - - the wait side of the same enter - [Run to quiescence](/quest/m1/perf/uring-quiescence.md) - fewer turns per packet, which multiplies this saving diff --git a/quest/m1/perf/uring-quiescence.md b/quest/m1/perf/uring-quiescence.md index 88b4879722..5ff5a19ba6 100644 --- a/quest/m1/perf/uring-quiescence.md +++ b/quest/m1/perf/uring-quiescence.md @@ -45,8 +45,3 @@ meaning with a budget of 1 and gains a sibling proving the budget bound. - [One enter per turn](/quest/m1/perf/uring-one-enter.md) - the metrics and the submit placement this sweep is measured with - -## Related - -- [Egress requeue](/quest/m1/perf/egress-requeue.md) - the train budget - measured in the same sweep diff --git a/quest/m1/performance-profiles.md b/quest/m1/performance-profiles.md index e0c5da304c..b9817b837b 100644 --- a/quest/m1/performance-profiles.md +++ b/quest/m1/performance-profiles.md @@ -45,5 +45,4 @@ verify current supported versions and pin any newly installed tools. ## Related - [Benchmark comparisons](/quest/m1/performance-comparisons.md) - repeatable results and artifact metadata -- [Relay memory](/quest/m1/relay-memory.md) - retained route and announcement memory - [Release profile](/quest/m1/release-profile.md) - also changes `[profile.profiling]`; land one, then rebase the other diff --git a/quest/m1/plan-watch-worker.md b/quest/m1/plan-watch-worker.md index 94f8afb7a6..5e39f90a19 100644 --- a/quest/m1/plan-watch-worker.md +++ b/quest/m1/plan-watch-worker.md @@ -75,4 +75,3 @@ follow-up wires the capture worklet to the encoder worker with a - [Watch worker](/quest/m1/watch-worker.md) - the implementation this rewrites - [Browser benchmarks](/quest/m1/browser-benchmarks.md) - artifact conventions; may absorb the harness later -- [A/V clock](/quest/m0/plan-av-clock.md) - the `Sync` shape the implementation moves; the prototype can pace against today's diff --git a/quest/m2/play-drain-tail.md b/quest/m1/play-drain-tail.md similarity index 100% rename from quest/m2/play-drain-tail.md rename to quest/m1/play-drain-tail.md diff --git a/quest/m1/qos/README.md b/quest/m1/qos/README.md index cc343a9c7c..96e59d38cd 100644 --- a/quest/m1/qos/README.md +++ b/quest/m1/qos/README.md @@ -44,9 +44,6 @@ on `main`. subscription records its last partial interval instead of losing it - [Lag dashboard](/quest/m1/qos/lag-dashboard.md) - the demo stats dashboard shows viewer lag percentiles and dropped media -- [Starvation at frame granularity](/quest/m1/qos/starvation-frames.md) - the - acknowledged frontier moves at every frame boundary through `poll_acked`, - with a delivery-delay histogram for jitter - [Publisher timeliness](/quest/m1/qos/publisher-timeliness.md) - per broadcast, how late media arrives at the relay against the track's own clock, and whether timestamps stay monotonic diff --git a/quest/m1/qos/starvation.md b/quest/m1/qos/starvation.md index 0a9131ad71..9e6c21d552 100644 --- a/quest/m1/qos/starvation.md +++ b/quest/m1/qos/starvation.md @@ -37,7 +37,7 @@ sample keeps it climbing the buckets while its frontier stands still. While the source is paused nothing is produced, so a tick carries no weight and the histogram does not move; a viewer's lag stays whatever it was until its frontier catches up, and reappears in the buckets once production resumes. This slice moves the frontier once per group; -the [frame-granularity quest](/quest/m1/qos/starvation-frames.md) moves it +the [frame-granularity quest](/quest/m2/starvation-frames.md) moves it per frame without changing the wire shape or the sampler, so fix both here. Aggregate as a byte-weighted cumulative histogram on `Traffic`, on the @@ -81,8 +81,6 @@ sum into one row; the aggregate consumer sums two nodes bucket by bucket. ## Related -- [Starvation at frame granularity](/quest/m1/qos/starvation-frames.md) - - moves the frontier at every frame end once `poll_acked` is released - [Publisher timeliness](/quest/m1/qos/publisher-timeliness.md) - the ingress mirror on the `Role::Subscriber` rows - [Media stats](/quest/m1/stats/README.md) - receiver-side evidence diff --git a/quest/m1/quic/README.md b/quest/m1/quic/README.md index 36c4c19aa2..251cdf92da 100644 --- a/quest/m1/quic/README.md +++ b/quest/m1/quic/README.md @@ -52,17 +52,9 @@ This is a transport API change, not a MoQ wire change. ## Required - [BBR idle burst](/quest/m1/quic/bbr-app-limited.md) - a fork regression proves a burst after a long idle is paced at the learned bandwidth, closing #4219 -- [Align BBR loss handling with draft-06](/quest/m1/quic/bbr-loss-parity.md) - losses use their own sample and undo re-enters ProbeUp through Refill - [Mark BBR starvation wherever the source runs dry](/quest/m1/quic/bbr-app-limited-edges.md) - partial polls count, local send caps do not, receiver credit is pinned - [Deliver the application close before io_uring teardown](/quest/m1/quic/uring-close.md) - the peer receives the final close when the client immediately stops its worker -- [Measure ECN on the backbone](/quest/m1/quic/ecn-measure.md) - a written - verdict on marking versus dropping, and whether Linode and OVH keep marks -- [Per-stream ACK progress](/quest/m1/quic/ack-progress.md) - the fork reports - how far a send stream has been acknowledged and when -- [poll_acked in web-transport](/quest/m1/quic/ack-hook.md) - the - backend-neutral hook that awaits an acknowledged stream offset, implemented - for noq and released - [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - `RESET_STREAM_AT`, so a reset WebTransport stream still delivers its header - [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) - strict @@ -70,12 +62,6 @@ This is a transport API change, not a MoQ wire change. the lossy scalar; retransmits follow the same order - [Relay peers get wider limits](/quest/m1/quic/peer-limits.md) - MAX_STREAMS and MAX_DATA are raised after SETUP identifies a cluster peer -- [Per-stream deadlines](/quest/m1/quic/deadline.md) - hopeless retransmits - become resets, and a tail loss probe fires early while there is still time -- [qmux on the QUIC stream state machine](/quest/m1/quic/qmux.md) - qmux is a - first-class crate in the fork over the shared stream state machine -- [Release the stack](/quest/m1/quic/release.md) - publish immutable, - consumable versions of the fork and its adapters - [Upstream the fork](/quest/m1/quic/upstream.md) - every general carried change is offered to n0-computer/noq once its shape has settled @@ -85,15 +71,6 @@ This is a transport API change, not a MoQ wire change. per-broadcast fairness policy on cluster sessions - [Starvation](/quest/m1/qos/starvation.md) - the first consumer of ACK progress: how far behind viewers are, from the relay's point of view -- [Receive timestamps](/quest/m2/quic-receive-ts.md) - per-packet arrival - times for GCC and deadlines -- [GCC egress experiment](/quest/m2/quic-gcc.md) - a measured verdict on - WebRTC-style delay control -- [FEC experiment](/quest/m2/quic-fec.md) - a measured verdict on transport - redundancy -- [Kernel pacing](/quest/m2/quic-kernel-pacing.md), [Send batching](/quest/m2/quic-send-batching.md), - [Send buffer pools](/quest/m2/quic-buffer-pool.md), [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - - the syscall, allocation, and controller spikes - [Multipath spike](/quest/m2/multipath-spike.md) - a noq capability that MoQ does not use yet - [Discover media headroom](/quest/m2/quic-probe.md) - test useful-media pacing before adding redundant probe traffic diff --git a/quest/m1/quic/release.md b/quest/m1/quic/release.md deleted file mode 100644 index fee1d41bdf..0000000000 --- a/quest/m1/quic/release.md +++ /dev/null @@ -1,32 +0,0 @@ -# [M] Release the QUIC stack - -## Goal - -Immutable releases of `moq-noq-proto`, `moq-noq`, `moq-noq-udp`, -`web-transport-moq`, and the qmux crate are available to every published MoQ -crate. The workspace lockfile identifies exact released sources, with no -root-only Cargo patch or mutable git branch, and a consumer can tell from any -release which parent commit it carries. - -## Plan - -Release the dependency chain from the bottom up: the fork's crates, which one -tag releases together, then `web-transport-trait` if its surface moved, then -qmux. Pin each released version in this repository's workspace dependencies -and regenerate `Cargo.lock`. Verify minimal, default, and all-feature builds so -enabling iroh, qmux, or the uring runtime cannot unify two incompatible copies -of the protocol state. - -Each fork release documents the parent commit, the carried patches with their -upstream PR or the reason there is none, and the security-update procedure. A -release is incomplete if a consumer cannot tell whether an advisory against -the parent applies. - -## Required - -- [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - the - WebTransport-required transport extension -- [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) - the new - transport API -- [qmux on the QUIC stream state machine](/quest/m1/quic/qmux.md) - the - shared qmux implementation diff --git a/quest/m1/quic/reliable-reset.md b/quest/m1/quic/reliable-reset.md index 10746d214f..22354b9dca 100644 --- a/quest/m1/quic/reliable-reset.md +++ b/quest/m1/quic/reliable-reset.md @@ -68,7 +68,5 @@ provisional codepoints if the document changes before release. - moq-net (`rs/moq-net/src/tail.rs`) and `@moq/net` (`js/net/src/tail.ts`) wait a grace for a group whose reset lost its header until this lands -- [qmux on the QUIC stream state machine](/quest/m1/quic/qmux.md) - consumes - the same reset state without a parallel implementation - The removed quiche backend was the one stack that had this, so it is the known browser-compliance gap. diff --git a/quest/m1/quic/scheduler.md b/quest/m1/quic/scheduler.md index 58bcb30b19..9745d5e96f 100644 --- a/quest/m1/quic/scheduler.md +++ b/quest/m1/quic/scheduler.md @@ -96,7 +96,7 @@ prove byte fairness over a bounded window, strict preemption by a higher priority, newest-first backlog shedding, dynamic priority updates, blocked-stream handling, sequence wrap and sparse sequence values, and cleanup on reset. This quest owns native QUIC proof and -reusable scheduling fixtures. [qmux](/quest/m1/quic/qmux.md) owns running those +reusable scheduling fixtures. [qmux](/quest/m2/quic-qmux.md) owns running those fixtures through its record writer after adopting the scheduler; native scheduler completion must not wait for that dependent integration. Preserve working behavior on backends not yet migrated, and remove queue code only @@ -112,7 +112,7 @@ where the new implementation makes it redundant. - [moq#3320](https://github.com/moq-dev/moq/pull/3320) - removes the current dense-rank queue from the wide scalar path and records why a scalar cannot provide this fairness level -- [Ladder controller](/quest/m1/ladder/controller.md) - rendition priority is +- [Ladder controller](/quest/m2/ladder/controller.md) - rendition priority is a policy consumer of the same hierarchy - [Scope track priority](/quest/m1/track-priority-scope.md) - owns the priority semantics this mechanism realizes, including the scheduling-domain diff --git a/quest/m1/quic/upstream.md b/quest/m1/quic/upstream.md index 294e9c0e44..97a6c22758 100644 --- a/quest/m1/quic/upstream.md +++ b/quest/m1/quic/upstream.md @@ -16,7 +16,7 @@ release and its shape has stopped moving, split it into an upstream PR with the tests it landed with. Offer the seven BBR correctness fixes (moq-noq 1.3.1, #4206) and -their [loss](/quest/m1/quic/bbr-loss-parity.md) and +their [loss](/quest/m2/quic-bbr-loss-parity.md) and [starvation](/quest/m1/quic/bbr-app-limited-edges.md) follow-ups with their regressions before promoting BBR as the default. Reuse existing upstream work, particularly [PR #802](https://github.com/n0-computer/noq/pull/802), @@ -34,11 +34,11 @@ Then the feature proposal order, each linked to its producing quest: since it adopted noq): a one-line change whose point is that every noq and iroh user, the maintainers included, runs the controller MoQ depends on, so its regressions are found upstream and not only here; -1. per-stream acknowledgment progress ([ACK progress](/quest/m1/quic/ack-progress.md)); +1. per-stream acknowledgment progress ([ACK progress](/quest/m2/quic-ack-progress.md)); 2. `RESET_STREAM_AT` ([reliable reset](/quest/m1/quic/reliable-reset.md)); 3. hierarchical send groups ([scheduler](/quest/m1/quic/scheduler.md)); -4. per-stream deadlines ([deadlines](/quest/m1/quic/deadline.md)); -5. the qmux crate over the shared stream state machine ([qmux](/quest/m1/quic/qmux.md)). +4. per-stream deadlines ([deadlines](/quest/m2/quic-deadline.md)); +5. the qmux crate over the shared stream state machine ([qmux](/quest/m2/quic-qmux.md)). The m2 features (keep-alive by deadline, careful resume as a `Controller` wrapper, ECT(1) marking, the media-headroom mechanism) are offered when they @@ -54,20 +54,13 @@ offered and answered. ## Required -- [Align BBR loss handling with draft-06](/quest/m1/quic/bbr-loss-parity.md) - [Mark BBR starvation wherever the source runs dry](/quest/m1/quic/bbr-app-limited-edges.md) -- [Per-stream ACK progress](/quest/m1/quic/ack-progress.md) - [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) -- [Per-stream deadlines](/quest/m1/quic/deadline.md) -- [qmux on the QUIC stream state machine](/quest/m1/quic/qmux.md) ## Related -- [Receive timestamps](/quest/m2/quic-receive-ts.md), [GCC](/quest/m2/quic-gcc.md), - [FEC](/quest/m2/quic-fec.md), [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - - experiments that join the list with a positive verdict - [Keep-alive by deadline](/quest/m2/quic-keep-alive.md), [Careful resume on reconnect](/quest/m2/quic-careful-resume.md), [L4S on the backbone](/quest/m2/quic-ecn.md), diff --git a/quest/m1/raw-stream-codes.md b/quest/m1/raw-stream-codes.md index f4e50e3373..0cd61c7f85 100644 --- a/quest/m1/raw-stream-codes.md +++ b/quest/m1/raw-stream-codes.md @@ -15,11 +15,6 @@ on stream errors. WebTransport sessions keep the mapping they need. `web-transport-iroh` and `web-transport-quinn` (moq-dev/web-transport) do the same. A raw peer's code 5 reads as `None` or another value, and ours reaches it as a large HTTP/3 code. -- [Close codes](/quest/m1/close-codes.md) fixes the same mix-up for - `ApplicationClosed` (noq#11 is released; #4262 is still open on `dev`, so - this follows it there unless trait 0.5 reaches main first). Let each stream know whether its - session is raw and skip the mapping there, in all three adapters, with a - round-trip test per adapter against a plain QUIC peer. - Release the fixed crates and bump the pins here in the same quest; published crates depend on crates.io releases, never a patch. A moq-tokio test over `moqt://` asserts a reset code arrives verbatim, beside `close_code.rs`. @@ -37,7 +32,3 @@ on stream errors. WebTransport sessions keep the mapping they need. Public API: none expected. Wire: raw QUIC stream error codes become the application's own values; compatible only if older peers merely report them. - -## Required - -- [Close codes](/quest/m1/close-codes.md) - the adapter releases and trait 0.5 this builds on diff --git a/quest/m1/relay-iroh-opt-in.md b/quest/m1/relay-iroh-opt-in.md index 51e2fa9b54..87b94ebc80 100644 --- a/quest/m1/relay-iroh-opt-in.md +++ b/quest/m1/relay-iroh-opt-in.md @@ -23,8 +23,3 @@ Guidance: ignored, whether it comes as a flag, an environment variable, or TOML. - Update `doc/bin/relay/` and any example that relies on the relay's iroh listener. Report the binary size difference in the PR. - -## Related - -- [`moq relay`](/quest/m1/moq-relay-subcommand.md) - forwards the relay's features from moq-cli's -- [P2P](/quest/m1/p2p/README.md) - why moq-cli keeps iroh diff --git a/quest/m1/relay-memory.md b/quest/m1/relay-memory.md deleted file mode 100644 index b4d9037702..0000000000 --- a/quest/m1/relay-memory.md +++ /dev/null @@ -1,41 +0,0 @@ -# [S] Relay memory per announcement - -## Goal - -A relay's memory scales with what it serves, not with what the mesh knows, -and the numbers behind that claim are current. Two costs to keep apart when -measuring: the route table and per-announcement state (`RouteEntry` plus -`ServeState`) scale with announcements and routes, while the served-content -cache a `ServeState` materializes scales with demand. - -## Plan - -Every published figure predates two changes and is untrustworthy: - -- [moq#2989](https://github.com/moq-dev/moq/pull/2989) cut `kio`'s inline - waiter slots from 32 to 4, so a `kio::State<()>` went from 896 B to about - 200 B. Going lower trades memory for an allocation per wake, which - `kio`'s `tests/waiter_allocs.rs` pins; that lever is spent. -- [moq#3225](https://github.com/moq-dev/moq/pull/3225) made a received - announcement a `RouteEntry` plus one `ServeState` whose cache materializes - a broadcast only when something requests a path. A standby route is a - table entry, not an object graph, so degree stopped being a multiplier. - -The old baseline was 8.8 KB per announced broadcast plus 4.3 KB per extra -route on `adad52b`, measured with two throwaway `moq-net` examples driving an -origin under a counting allocator and reading `/proc/self/statm`. Neither is -committed, since they need `#[doc(hidden)]` size probes on private types. -Rebuild them from this description and restate the per-broadcast and -per-route cost, the per-peer session bookkeeping (`announce_ids`, `held`, -`watched`), and the shed threshold on a degree-5, 1 GB node, before anyone -quotes a number again. Chat-shaped traffic (one broadcast per channel or -per chatter) depends on the answer. - -On-demand announcements are now [Cluster routing](/quest/m1/cluster-routing.md)'s -plan: a relay learns only the prefixes its own clients request, which bounds -the table by demand. These numbers size that saving. - -## Related - -- [Cluster routing](/quest/m1/cluster-routing.md) - on-demand announcements shrink the table this measures -- [Perf](/quest/m1/perf/README.md) - the hot-path work that owns the remaining per-cell cost diff --git a/quest/m1/remove-live.md b/quest/m1/remove-live.md deleted file mode 100644 index 80be20255d..0000000000 --- a/quest/m1/remove-live.md +++ /dev/null @@ -1,49 +0,0 @@ -# [M] Importers publish stream timestamps; the catalog clock maps them to wall - -## Goal - -The fMP4, MPEG-TS, and FLV importers in `rs/moq-mux` have no `live()`: every -importer publishes the stream's own timestamps verbatim (after PTS unwrap), -and the catalog's root `clock` is what maps them to wall time. `moq import` -(`rs/moq-cli/src/publish.rs`) stops calling it. An encoder that restarts its -timestamps ends the broadcast with an error instead of being re-anchored -forward onto the old one; turning the republish into a new epoch is the -broadcast epoch line's outcome, not this quest's. The SRT, RTMP, and HLS gateways, which reuse these -importers, get the same behavior. - -## Plan - -Decided (2026-09-28, replacing the gateway live-clock quest, which planned -the opposite: every gateway opting into `live()`): - -- Timestamps stay verbatim from the stream. Rewriting them onto an - arrival-time anchor breaks same-hop importers, which must derive every - timestamp from the input alone (the hop-aligned import quest in - [#4388](https://github.com/moq-dev/moq/pull/4388)), and hides the source's - own timeline from anything downstream. -- The catalog clock carries the mapping. An importer establishes the root - `clock` so the stream's PTS converts to wall time (the first frame is live - on arrival), rather than translating each timestamp. Open: whether the - importer sets the catalog clock from its first frame (the mapping is fixed - at construction today, `moq_mux::Clock` and `catalog::Config::with_clock`) - or the caller builds the catalog once the first PTS is known. Every track of - one input, and every rendition of one HLS import, shares that one mapping. -- An encoder restart (a PTS rewind or a signalled time-base discontinuity) is - a new epoch, not a forward re-anchor: the importer ends the broadcast with - an error, and the caller republishes, which the broadcast epoch line turns - into a fresh `@`. Until that line lands, a restart fails loud. -- Delete `live()` from `ts::Import`, `fmp4::Import`, and `flv::Import`, the - crate-private `clock::Anchor`, and `SourceMap` if nothing else uses it. - fMP4 passthrough stops rewriting `tfdt`. A published `moq-mux` API break, - so this targets `dev`. - -Tests: per importer, a source starting at a large PTS publishes that PTS and -a catalog clock that maps it to near the arrival time; a rewind ends the -broadcast with an error. Update `doc/lib/rs/moq-mux.md` (the `live()` -paragraph), `doc/bin/cli.md`, and the gateway pages under `doc/bin/`, and -replace `ts_import_publishes_on_the_broadcast_clock` in moq-cli. - -## Related - -- [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - the new epoch an encoder restart becomes -- [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - the TS re-anchor shift that must stay input-derived diff --git a/quest/m1/rs2ts/README.md b/quest/m1/rs2ts/README.md index e37edf9eb0..f152d90870 100644 --- a/quest/m1/rs2ts/README.md +++ b/quest/m1/rs2ts/README.md @@ -59,7 +59,6 @@ js/net it replaces, measured with the [browser benchmarks](/quest/m1/browser-ben - [Sans-IO moq-net](/quest/m1/rs2ts/sans-io/README.md) - moq-net builds and runs without a runtime; async helpers sit behind an `async` feature - [Mock-clock tests](/quest/m1/rs2ts/mock-clock.md) - moq-net's tests run on the sans-IO clock instead of tokio, so they translate with the code - [Generated lite](/quest/m1/rs2ts/lite.md) - @moq/net's lite session and model layer are generated from moq-net -- [Generated IETF](/quest/m1/rs2ts/ietf.md) - @moq/net's moq-transport session is generated too - [Remove moq-wasm](/quest/m1/rs2ts/remove-wasm.md) - the WASM experiment is deleted once generated lite ships ## Closes diff --git a/quest/m1/rs2ts/sans-io/README.md b/quest/m1/rs2ts/sans-io/README.md index 03a4cfb82b..2795d86d47 100644 --- a/quest/m1/rs2ts/sans-io/README.md +++ b/quest/m1/rs2ts/sans-io/README.md @@ -20,4 +20,3 @@ The line has no work of its own beyond its children. - [Sans-IO lite session](/quest/m1/rs2ts/sans-io/lite.md) - the lite session is driven by bytes, stream events, and `tick(now)` - [Sans-IO model](/quest/m1/rs2ts/sans-io/model.md) - origin, broadcast, track, and group handles run without a runtime, with time supplied by the caller - [The async feature](/quest/m1/rs2ts/sans-io/async-feature.md) - the async helpers sit behind an `async` feature and a CI lane builds and tests moq-net without it -- [Sans-IO IETF session](/quest/m1/rs2ts/sans-io/ietf.md) - the moq-transport session is driven the same way as lite diff --git a/quest/m1/rs2ts/sans-io/async-feature.md b/quest/m1/rs2ts/sans-io/async-feature.md index f7b7e35046..586cc5309e 100644 --- a/quest/m1/rs2ts/sans-io/async-feature.md +++ b/quest/m1/rs2ts/sans-io/async-feature.md @@ -11,7 +11,7 @@ crate that [Generated lite](/quest/m1/rs2ts/lite.md) translates. - The feature is on by default, so Rust callers see no change. JS reimplements the helpers with Promises over the poll API. - Generated lite needs the lite session and the model without the feature, - not IETF. Until the [Sans-IO IETF session](/quest/m1/rs2ts/sans-io/ietf.md) + not IETF. Until the [Sans-IO IETF session](/quest/m2/rs2ts-sans-io-ietf.md) lands, the IETF session can sit behind the feature too; that quest then moves it out. - The lane runs at least the tests that do not exercise the helpers; tests diff --git a/quest/m2/serve-hls-renditions.md b/quest/m1/serve-hls-renditions.md similarity index 100% rename from quest/m2/serve-hls-renditions.md rename to quest/m1/serve-hls-renditions.md diff --git a/quest/m1/session-close.md b/quest/m1/session-close.md deleted file mode 100644 index 1ac4d9d92e..0000000000 --- a/quest/m1/session-close.md +++ /dev/null @@ -1,34 +0,0 @@ -# [M] Graceful session close withdraws announces - -## Goal - -Ending a session on purpose withdraws the namespaces it published and waits -for the peer to acknowledge that, up to one second. `abort`, and dropping the -last handle, still end the session immediately and do not wait. - -## Plan - -Rust has `abort` and drop, and both end the session now. Drop sends a bare -`Cancel`, so `PUBLISH_NAMESPACE_DONE` never goes out. -`moq_net::Session::close()` and `moq_tokio::Connection::close()` already -exist: they wait up to `CLOSE_TIMEOUT` (one second, in `moq-net`'s -`session.rs`) for finished tracks to deliver, then end the session, returning -`Error::Timeout` if the peer was too slow. Extend that drain phase to -unannounce what this session published and wait for the acknowledgements, -under the same deadline. Only moq-lite drains today; IETF sessions close at -once. - -JS `Established.close()` is synchronous today. It becomes the same graceful -end and returns a promise, which is a published break, so that change targets -`dev`. `abort` stays immediate in both languages. - -`doc/concept/moq-lite.md` says a graceful close withdraws announces and an -abort does not. No new page. - -The interop runner is the consumer that found this -([#4209](https://github.com/moq-dev/moq/pull/4209)): after a successful -publish it calls `abort`, so the relay never sees `PUBLISH_NAMESPACE_DONE` and -the next run is told the namespace is already published. Switch it to the -graceful `close()` once that exists. If the calling code lives outside this -repository (moq-interop-runner), that change is a PR there and needs the -maintainer's approval before posting. diff --git a/quest/m1/session-death.md b/quest/m1/session-death.md index af67ddd715..2d848eebcf 100644 --- a/quest/m1/session-death.md +++ b/quest/m1/session-death.md @@ -40,7 +40,3 @@ Extend the existing session-death tests (Rust integration cases) with a local close and with a group reader, on lite and IETF. Behavior change, no signature change on either side unless the Rust clean end needs a public method. - -## Related - -- [Graceful session close](/quest/m1/session-close.md) - what a local close sends the peer diff --git a/quest/m1/stats-producer-bench.md b/quest/m1/stats-producer-bench.md deleted file mode 100644 index 869404f06b..0000000000 --- a/quest/m1/stats-producer-bench.md +++ /dev/null @@ -1,33 +0,0 @@ -# [S] Stats producer fan-out benchmark - -## Goal - -A `moq-stats` benchmark measures what one relay pays per stats tick to drain -its registry and encode the traffic tracks, swept over held paths and tiers, -so a cost that grows with the whole table instead of the paths that changed -shows up as a slope. It runs at least nightly. - -## Plan - -- [#4299](https://github.com/moq-dev/moq/pull/4299) keeps an idle path in - every frame while the registry holds its counters, adding about 430 B per - idle path to each plain frame (maintainer's note on the PR). Nothing - measures what that, or the per-tick drain, costs as held paths grow. - `rs/moq-stats/benches/decode.rs` covers only the reader. -- Sweep held paths (for example 100 to 50k) against tiers, and the share of - paths that are idle versus changed this tick. Report time and allocations - per tick, and plain and compressed bytes per frame, the way `decode.rs` - reports its table. Include the point where a plain frame nears its size - cap, since a frame that is too large leaves stale counters behind. -- Drive the real producer path (`process_slot`, the snapshot encoders) over - a synthetic `Registry`, not a re-implementation of it. Exposing a bench - hook is fine if it stays out of the public API. -- `decode.rs` is not in CI either. Run both targets once in the nightly - benchmark smoke, as `moq-net`'s are. - -Public API: none. Wire: none. - -## Related - -- [Binary delta stats flavor](/quest/m2/stats-delta.md) - its gate needs the encode baseline this measures -- [Benchmark regressions in CI](/quest/m1/bench-ci.md) - compares these targets across PRs once it lands diff --git a/quest/m1/stats/README.md b/quest/m1/stats/README.md index 24828d41bc..02d31b0fc0 100644 --- a/quest/m1/stats/README.md +++ b/quest/m1/stats/README.md @@ -97,7 +97,7 @@ kind. `doc/concept/stats.md` gains a media section beside the relay's, and `drafts/draft-lcurley-moq-hang.md` specs the wire. -Open, to settle before [encoder feedback](/quest/m1/stats/encoder-feedback.md) +Open, to settle before [encoder feedback](/quest/m2/stats-encoder-feedback.md) starts: - **Referenced-rendition feedback.** A derivative catalog (a `moq-transcode` @@ -124,8 +124,6 @@ starts: moq-mux remuxes publish stats and feedback - [Browser reporters](/quest/m1/stats/js.md) - `` publishes stats and `` publishes feedback -- [Encoder feedback](/quest/m1/stats/encoder-feedback.md) - a Rust encoder - reads its viewers' feedback and adapts its bitrate ## Related diff --git a/quest/m1/track-demand.md b/quest/m1/track-demand.md deleted file mode 100644 index 6dc5cea5bc..0000000000 --- a/quest/m1/track-demand.md +++ /dev/null @@ -1,22 +0,0 @@ -# [M] Track demand is watched through demand() alone - -## Goal - -In Rust and JS, a track's subscribers are watched only through its `Demand`: -`track::Producer` drops `is_used`/`used`/`unused` and every layer producer -built on a track (moq-json, moq-mux import, moq-audio, moq-binary) exposes -`demand()` instead of its own copies. Group and broadcast `used`/`unused` stay; -`Demand` is a track concept, and group demand drives fetch coalescing. - -## Plan - -About 150 call sites move, across moq-net, moq-mux, moq-json, moq-audio, -moq-binary, moq-relay, moq-transcode, moq-stats, and libmoq. Both waits -already surface the track's abort reason, so callers keep their errors. Keep -`abort_unused` if its race still needs an owner. JS mirrors the Rust shape in `js/net`. - -Lands on `dev` alongside the [FFI shape](/quest/m1/ffi-shape/README.md) -line, so moq-net breaks once. - -Public API: breaking in moq-net and the layer crates, and in `@moq/net`. Wire: -none. diff --git a/quest/m1/ts-import-shared-shift.md b/quest/m1/ts-import-shared-shift.md index 2ae0bf3397..acdf597249 100644 --- a/quest/m1/ts-import-shared-shift.md +++ b/quest/m1/ts-import-shared-shift.md @@ -44,7 +44,3 @@ Guidance: - Tests beside the existing loop-wrap tests: a muxed H.264 + AAC loop whose period is not a multiple of either frame duration keeps the first audio and video timestamps of each pass at the source offset across three wraps. - -## Related - -- [Remove live()](/quest/m1/remove-live.md) - deletes the restart anchor; a wrap shift stays input-derived diff --git a/quest/m1/unknown-session-logs.md b/quest/m1/unknown-session-logs.md deleted file mode 100644 index b1db43f89a..0000000000 --- a/quest/m1/unknown-session-logs.md +++ /dev/null @@ -1,46 +0,0 @@ -# [S] UnknownSession log flood - -## Goal - -moq.pro relays stop logging -`web_transport_moq::session: failed to decode unidirectional stream err=WebTransportError(UnknownSession)` -for streams that were simply reset or cut off before their WebTransport -header arrived. A stream that really names another session is still reported -as `UnknownSession`, and the error a caller sees says what happened. - -## Plan - -Seen in production at a rate like the old-group warnings -https://github.com/moq-dev/moq/pull/4208 fixed, on the same hosts. - -Root cause, confirmed by a test: `decode_uni` and `decode_bi` in -`web-transport-moq`'s `session.rs` map every failure to read the stream type -or session ID to `UnknownSession`, and a peer reset before those bytes -arrive is one. moq resets a group's stream when the group is superseded or -expires, and without reliable reset the header is discarded with it. - -The fix keeps the read's cause as `WebTransportError::ReadError`, keeps -`UnknownSession` for a session-ID mismatch, and logs a reset or lost -connection at debug: - -- [moq-dev/noq#19](https://github.com/moq-dev/noq/pull/19) for 2.x (`dev`) - and its backport [moq-dev/noq#20](https://github.com/moq-dev/noq/pull/20) - for 1.3.x (`main`). -- [moq-dev/web-transport#405](https://github.com/moq-dev/web-transport/pull/405) - for `web-transport-quinn`, `web-transport-noq`, and `web-transport-iroh`, - which carry the same code. `main` pins `web-transport-iroh` 0.7, so it - gets the iroh fix when `dev` merges. - -Remaining: bump `web-transport-moq` to the 1.3.3 release on `main` (and -2.0.1 on `dev` if it lands first). Then confirm on a moq.pro relay that the -rate drops, and that any remaining `UnknownSession` lines are real -mismatches. - -## Required - -- `web-transport-moq` 1.3.3 released from moq-dev/noq#20 - -## Related - -- [Close codes on every transport](/quest/m1/close-codes.md) - another error mapping fix in the same crate -- [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - keeps a reset stream's header, which removes most of these diff --git a/quest/m1/uring-ietf.md b/quest/m1/uring-ietf.md index c9f9cbcb31..13cc9cfbb7 100644 --- a/quest/m1/uring-ietf.md +++ b/quest/m1/uring-ietf.md @@ -28,8 +28,3 @@ gap is in the worker's accept path, not in moq-net. Additive, so it lands on main. moq.pro's fleet deploy of the ring requires the release carrying it. - -## Related - -- [Stream sessions](/quest/m1/uring-tcp/README.md) - the other protocol gap - on the ring, WebSocket and HTTP diff --git a/quest/m1/video-surface.md b/quest/m1/video-surface.md deleted file mode 100644 index d5e7cdc9f5..0000000000 --- a/quest/m1/video-surface.md +++ /dev/null @@ -1,36 +0,0 @@ -# [XS] FFI decoded frames expose a surface, only where one exists - -## Goal - -moq-ffi names the decoder's retained picture the way moq-video does, and a -caller cannot opt into it on a platform that has none. On `dev` -([#4094](https://github.com/moq-dev/moq/pull/4094)) the frame's view is -`MoqVideoNative` from `native()`, enabled by `MoqVideoDecoderOutput.native`. -Only macOS has a variant (`PixelBuffer`). Elsewhere the opt-in still decodes -to native surfaces, `native()` always returns `None`, and a tiled VAAPI -DMA-BUF also fails `pixels()`, so the caller gets frames it cannot read. - -## Plan - -Decided: - -- Rename to surface naming, mirroring `moq_video::Surface`: - `MoqVideoSurface`, `surface()`, and the matching `MoqVideoDecoderOutput` - flag. "Native" named today's implementation, not the role. -- Refuse the opt-in at subscribe time on platforms with no surface variant - (Windows and Linux today) with a clear unsupported error. Each platform - lifts the refusal when its variant lands with hardware proof, in - [decode-windows](/quest/m1/obs-moq-video/decode-windows.md) and - [decode-linux](/quest/m1/obs-moq-video/decode-linux.md). - -Guidance: - -- Whether `moq_video::Output::Native` should follow the rename is a - separate call; ask before touching it. -- Update the wrappers and docs per the root `AGENTS.md` sync table: the Go, - Python, Swift, and Kotlin option docs mention the native surface flag, and - `quest/m1/obs-moq-video/source.md` and the release plan on `dev` name - `native`. -- A `dev` break on top of #4094, so it rides the same release. -- Test: the opt-in is refused where there is no variant, and on macOS - `surface()` returns the pixel buffer. diff --git a/quest/m1/watch-audio-time-stretch.md b/quest/m1/watch-audio-time-stretch.md index 0ceb0850b6..34134a7e21 100644 --- a/quest/m1/watch-audio-time-stretch.md +++ b/quest/m1/watch-audio-time-stretch.md @@ -12,7 +12,7 @@ Boundaries: no packet loss concealment; an underrun still renders a ramped gap. The target estimator and the ring's slack and re-stall are the [audio jitter target](/quest/m0/audio-jitter-target/README.md) line (#3517 was closed in favor of it); the clock the stretch converges toward is -[Plan: A/V clock](/quest/m0/plan-av-clock.md). +[Plan: A/V clock](/quest/m1/av-clock.md). ## Plan @@ -28,5 +28,4 @@ was closed in favor of it); the clock the stretch converges toward is ## Required -- [Plan: A/V clock](/quest/m0/plan-av-clock.md) - stretching against a free-running ring only moves the drift - [Watch](/quest/m0/audio-jitter-target/watch.md) - the recorded traces this replays, and the JS target it converges toward diff --git a/quest/m1/watch-refusal.md b/quest/m1/watch-refusal.md deleted file mode 100644 index 4412cf6113..0000000000 --- a/quest/m1/watch-refusal.md +++ /dev/null @@ -1,38 +0,0 @@ -# [S] Watch shows a refusal - -## Goal - -When the origin refuses the broadcast `` asks for, the player shows -that refusal as an error instead of sitting offline as if nothing was -published yet. Refusal stays terminal, as -[#4230](https://github.com/moq-dev/moq/pull/4230) made it in `@moq/net` to -match Rust moq-net: the player never re-asks a handler that already said no. - -## Plan - -- The gap is Codex's P1 on #4230 - ([r4109909244](https://github.com/moq-dev/moq/pull/4230#discussion_r4109909244)): - `js/watch/src/broadcast.ts` only watches `request.active`, so after a - `dynamic()` handler refuses, the request closes with an error that nobody - reads and `active` stays `undefined` forever. -- Observe `Requesting.closed` and carry the error into the broadcast's - state. Whether that is a new `"error"` status, a separate error signal, or - both is open; mirror how the element already surfaces other terminal - states, such as the unsupported indicator. Keep the error's message so the - UI can say why. `unroutable` is also true for a path nothing serves yet, so - it cannot tell a refusal from offline. -- What clears the error is part of the design: a fresh request (a new - `name` or origin, or re-enabling) should be the only way back. No retry - loop. -- Cover both the announced and unannounced paths in `#runBroadcast`. -- Show it in the UI, and update `demo/web` if it consumes the status. Add a - test in `js/watch` where a `dynamic()` handler refuses and the broadcast - reports the error. -- Update `doc/` wherever the watch status values are documented. - -Public API: likely additive (a new status value or error signal on the watch -broadcast and element). Wire: none. - -## Related - -- [#4230](https://github.com/moq-dev/moq/pull/4230) - made JS refusals terminal diff --git a/quest/m1/watch-worker.md b/quest/m1/watch-worker.md index ccbbc14bc7..21501340e4 100644 --- a/quest/m1/watch-worker.md +++ b/quest/m1/watch-worker.md @@ -31,7 +31,6 @@ Gate on the plan's jank harness and N-player sweep, both nightly. ## Required - [Plan: watch worker](/quest/m1/plan-watch-worker.md) - picks the worker model and rewrites this quest -- [A/V clock](/quest/m0/plan-av-clock.md) - reshapes `Sync` and the worklet playhead, so the move to the worker happens once ## Related diff --git a/quest/m1/wt-close-upstream.md b/quest/m1/wt-close-upstream.md index a277c17cb0..2b4d76f176 100644 --- a/quest/m1/wt-close-upstream.md +++ b/quest/m1/wt-close-upstream.md @@ -34,8 +34,3 @@ Decided 2026-09-29: fix it at the source and remove the timeout workaround. moq-tokio. Public API: none. Wire: none. - -## Related - -- [Close codes on every transport](/quest/m1/close-codes.md) - the same symptom over qmux and raw QUIC -- [UnknownSession log flood](/quest/m1/unknown-session-logs.md) - another `web-transport-moq` release and pin bump diff --git a/quest/m1/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md b/quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md similarity index 88% rename from quest/m1/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md rename to quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md index 65fd5ba4cb..eacb636ae1 100644 --- a/quest/m1/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md +++ b/quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md @@ -9,7 +9,7 @@ exporter understands. Server-side ad insertion is a separate future quest. ## Plan -Use the shared event contract [emsg](/quest/m1/emsg.md) settles. Deliver cues immediately, including when splice_time is in the +Use the shared event contract [emsg](/quest/m2/emsg.md) settles. Deliver cues immediately, including when splice_time is in the future; consumers need advance notification. Metadata group sequences are independent of media GOPs. @@ -40,14 +40,6 @@ schema: the draft is the normative spec, and a section Rust and JS emit must be in it the day it ships. Cross-package sync: `rs/hang`, `js/hang`, `rs/moq-mux`, `doc/concept`. -## Required - -- [fMP4 emsg](/quest/m1/emsg.md) - settles the shared framing and missing-data semantics before this section adopts them - ## Closes - [#2279](https://github.com/moq-dev/moq/issues/2279) - close this issue when the quest finishes - -## Related - -- [ID3 catalog section](/quest/m1/id3.md) - the other typed timed-metadata section, same rule diff --git a/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md b/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md index d72e102a55..9b6eaad34f 100644 --- a/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md +++ b/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md @@ -39,4 +39,3 @@ Refs #2481, #1837. ## Related - [Capture multi-plane PipeWire cameras](/quest/m2/pipewire-camera-planes.md) - separate memory blocks from a camera, the capture offer rather than this import -- [#2893: video: validate PipeWire DMA-BUF capture on KDE hardware](/quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md) - the KDE portal capture that timed out diff --git a/quest/m1/perf/3129-moq-uring-write-the-webtransport-stream-header-at-open.md b/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md similarity index 94% rename from quest/m1/perf/3129-moq-uring-write-the-webtransport-stream-header-at-open.md rename to quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md index 67e260a184..d9ae6fa649 100644 --- a/quest/m1/perf/3129-moq-uring-write-the-webtransport-stream-header-at-open.md +++ b/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md @@ -31,10 +31,6 @@ The reason it is not a small change: `poll_open_uni` would have to hold a half-o Worth confirming the trade too: making `open` block on credit moves the backpressure earlier, which is more correct but changes when a caller learns about it. -## Required - -- [Open contract](/quest/m1/perf/uring-open-contract.md) - settle concurrent ownership and backpressure before implementation - ## Closes - [#3129](https://github.com/moq-dev/moq/issues/3129) - close this issue when the quest finishes diff --git a/quest/m1/perf/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md b/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md similarity index 100% rename from quest/m1/perf/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md rename to quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md diff --git a/quest/m1/perf/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md b/quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md similarity index 100% rename from quest/m1/perf/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md rename to quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md diff --git a/quest/m2/README.md b/quest/m2/README.md index 95f0845640..b915f3276a 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -15,26 +15,63 @@ upstream release waits in [m4](/quest/m4/README.md). ## Required +- [P2P](/quest/m2/p2p/README.md) - opted-in clients serve each other over data channels and iroh while the relay stays the rendezvous and the fallback, under application policy +- [One port](/quest/m2/one-port/README.md) - a relay speaks QUIC, STUN, WebRTC media, and SRT on one UDP port and HTTP, RTMP, and RTMPS on one TCP port +- [Ladder](/quest/m2/ladder/README.md) - a transcode ladder adapts to the uplink it publishes over, instead of encoding every live rung at its ceiling +- [Processor](/quest/m2/processor/README.md) - a customer-run worker publishes an on-demand contribution under its own service prefix with scoped access +- [Stream sessions](/quest/m2/uring-tcp/README.md) - serve WebSocket and HTTP from the io_uring workers, where io_uring pays off most +- [fMP4 emsg](/quest/m2/emsg.md) - settles the shared framing and missing-data semantics before this section adopts them +- [ID3 catalog section](/quest/m2/id3.md) - timed ID3 as a first-class container-neutral catalog section +- [FLV script tags](/quest/m2/flv-script.md) - onMetaData and AMF data messages survive RTMP and FLV import +- [#2279](/quest/m2/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/m2/captions-cea.md) - captions carried inside video SEI become a real text rendition at import +- [Browser archive](/quest/m2/archive-browser.md) - the same contract for browser-published broadcasts +- [Paced replay](/quest/m2/archive-paced-replay.md) - a replay pushes its groups to live subscribers on one shared clock, so any live player plays it +- [Install moq](/quest/m2/moq-installer.md) - one command installs or upgrades the released CLI on macOS and Linux +- [Install URL](/quest/m2/moq-install-url.md) - moq.dev serves the canonical installer at /install.sh +- [`moq relay`](/quest/m2/moq-relay-subcommand.md) - the relay runs under a `moq` verb with its own flags and TOML, while `moq-relay` stays a minimal binary +- [Linux decoded frames](/quest/m2/obs-decode-linux.md) - present supported native decoded surfaces with visible CPU fallback +- [Windows decoded frames](/quest/m2/obs-decode-windows.md) - present decoded D3D11 surfaces in OBS without CPU readback +- [macOS GPU input](/quest/m2/obs-macos.md) - feed the encoder from the OBS compositor without CPU readback +- [Windows GPU input](/quest/m2/obs-windows.md) - import or blit OBS D3D11 textures with explicit synchronization +- [Sans-IO IETF session](/quest/m2/rs2ts-sans-io-ietf.md) - the session shape it translates +- [Generated IETF](/quest/m2/rs2ts-ietf.md) - @moq/net's moq-transport session is generated too +- [Per-stream deadlines](/quest/m2/quic-deadline.md) - hopeless retransmits + become resets, and a tail loss probe fires early while there is still time +- [qmux on the QUIC stream state machine](/quest/m2/quic-qmux.md) - qmux is a + first-class crate in the fork over the shared stream state machine +- [Align BBR loss handling with draft-06](/quest/m2/quic-bbr-loss-parity.md) - losses use their own sample and undo re-enters ProbeUp through Refill +- [Measure ECN on the backbone](/quest/m2/quic-ecn-measure.md) - a written + verdict on marking versus dropping, and whether Linode and OVH keep marks +- [Starvation at frame granularity](/quest/m2/starvation-frames.md) - the + acknowledged frontier moves at every frame boundary through `poll_acked`, + with a delivery-delay histogram for jitter +- [Per-stream ACK progress](/quest/m2/quic-ack-progress.md) - the fork reports + how far a send stream has been acknowledged and when +- [poll_acked in web-transport](/quest/m2/quic-ack-hook.md) - the + backend-neutral hook that awaits an acknowledged stream offset, implemented + for noq and released +- [#3200](/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md) - moq-uring: batch completion wakeups with MIN_TIMEOUT +- [#3202](/quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md) - moq-uring: use fixed-file slots for worker UDP sockets +- [Open contract](/quest/m2/uring-open-contract.md) - settle concurrent ownership and backpressure before implementation +- [#3129](/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md) - moq-uring: write the WebTransport stream header at open time, so finish() never owes one +- [Cache shard](/quest/m2/cache-shard.md) - stop hammering one process-global cache line from every worker +- [Encoder feedback](/quest/m2/stats-encoder-feedback.md) - a Rust encoder + reads its viewers' feedback and adapts its bitrate +- [Text availability](/quest/m2/text-schema.md) - a text track publishes its own coverage index instead of copying the media timeline +- [Closure counters](/quest/m2/closure-counters.md) - a departed node's return never regresses the closure counters a consumer already saw +- [Bench coverage](/quest/m2/bench-coverage.md) - Criterion targets for moq-mux containers, the hang catalog, moq-auth verification, and moq-pattern matching +- [Signed priority](/quest/m2/signed-priority.md) - on dev, every API priority is an `i8` with 0 as the unset midpoint, and hang's built-ins sit above it - [AV1 metadata separation](/quest/m2/av1-metadata.md) - retain metadata OBUs inline while evaluating separate delivery -- [SEI separation](/quest/m2/sei/README.md) - retain inline SEI until measured savings or a metadata-only consumer justify a split - [Catalog track identity](/quest/m2/catalog-tracks.md) - compare immutable track definitions with explicit catalog-to-group binding - [Archive recovery listing](/quest/m2/archive-recovery-listing.md) - a resumed DVR lists what changed since its checkpoint, not every stored group -- [Archive backward timestamps](/quest/m2/archive-backward-timestamps.md) - a resumed recording refuses a track whose timestamps go backward -- [IETF drain before close](/quest/m2/ietf-drain-before-close.md) - moq-transport sessions deliver finished tracks before a graceful close, as moq-lite does -- [moq play drain tail](/quest/m2/play-drain-tail.md) - retired renditions and finite tracks play their last 10 ms of audio - [Relay io_uring packages](/quest/m2/relay-io-uring-package.md) - Linux relay packages ship io_uring once the ring is on par with tokio -- [Mobile ownership](/quest/m2/mobile-ownership.md) - decide whether Rust or platform code owns mobile capture, codecs, and rendering - [iOS capture](/quest/m2/mobile-capture-ios.md) - camera and screen capture if the mobile ownership decision selects Rust - [Android capture](/quest/m2/mobile-capture-android.md) - NDK/JNI capture using the existing codecs if mobile ownership selects Rust - [Mobile completion](/quest/m2/mobile-completion.md) - verify the selected native/mobile path before closing #700 -- [Linux OBS GPU input](/quest/m2/obs-linux-gpu.md) - publish OBS compositor frames without CPU readback on a validated Linux graphics/encoder combination -- [LiveKit client shim](/quest/m2/livekit-shim.md) - a media compatibility facade over the room SDK -- [Audio loss recovery](/quest/m2/audio-loss-recovery.md) - prove a useful Opus recovery policy before exposing another option - [Opus implementation](/quest/m2/audio-opus-backend.md) - compare current codec quality, CPU, and optional build costs - [Latency ledger](/quest/m2/latency-ledger.md) - a session reports where its end-to-end audio delay went, stage by stage - [JS LOC duration marker](/quest/m2/js-loc-duration-marker.md) - `@moq/loc`'s producer ends each video group with the empty duration frame, as moq-mux does -- [JS discontinuity](/quest/m2/js-discontinuity.md) - on dev, JS `discontinuity()` without an end writes no cadence-estimated end, like Rust -- [Synced data playback](/quest/m2/watch-data-sync.md) - js/watch releases JSON and binary payloads on the media playhead, and a slow data track holds media back - [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - Windows decodes HE-AAC, multichannel AAC, and what else the MFTs offer - [Media Foundation encode](/quest/m2/audio-encode-mediafoundation.md) - Windows encodes AAC-LC - [MediaCodec decode](/quest/m2/audio-decode-mediacodec.md) - Android decodes HE-AAC, multichannel AAC, and what else the device offers @@ -47,50 +84,31 @@ upstream release waits in [m4](/quest/m4/README.md). - [Intra-refresh GOPs](/quest/m2/intra-refresh/README.md) - video with periodic intra refresh publishes, imports, and tunes in cleanly with one group per sweep and a catalog `warmup` - [Capture multi-plane PipeWire cameras](/quest/m2/pipewire-camera-planes.md) - I420 and NV12 cameras that deliver one memory block per plane - [#2819](/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md) - moq-video: validate PipeWire DMA-BUFs into the Vulkan renderer on hardware, and export V4L2 buffers as DMA-BUFs -- [Unreal prototype](/quest/m2/unreal.md) - a UE5 module on the C++ package with exceptions disabled, rendering a subscribed broadcast to a texture -- [Unity prototype](/quest/m2/unity.md) - the C# package under IL2CPP, playing subscribed audio -- [C# through moq-ffi](/quest/m2/cs/README.md) - generated C# over moq-ffi as a NuGet package with native runtimes - [vcpkg registry](/quest/m2/cpp-vcpkg.md) - a registry we own serves the prebuilt package to `vcpkg` manifests -- [Conan remote](/quest/m2/cpp-conan.md) - a remote we own serves the same tarball to `conan install` -- [Compressed tracks](/quest/m2/flate/README.md) - the hand-written binding wrappers expose flate tracks - [Binary delta stats](/quest/m2/stats-delta.md) - an on-demand varint delta flavor of every stats track, if relay encode CPU still matters after the JSON fixes - [#3115](/quest/m2/3115-moqsink-the-publication-has-no-generation-so-a-flush.md) - moqsink: a flushing restart after EOS opens a new publication generation -- [Redundant ingest](/quest/m2/redundant-ingest.md) - decide whether two publishers sharing one epoch may splice, and who declares the incumbent dead before the keep-alive does - [Multipath spike](/quest/m2/multipath-spike.md) - whether bonded contribution over multipath QUIC is worth building, given it needs noq on both ends -- [Receive timestamps](/quest/m2/quic-receive-ts.md) - per-packet arrival times in ACKs, the feedback GCC and deadlines need -- [QUIC GCC](/quest/m2/quic-gcc.md) - a measured verdict on delay-based congestion control for media egress, shipping as `RealTime` -- [QUIC FEC](/quest/m2/quic-fec.md) - a measured verdict on transport-level FEC vs retransmission -- [Google BBR comparison](/quest/m2/quic-bbr-google.md) - measure growth detection and precautionary probing after the correctness fixes -- [WHEP ABR](/quest/m2/whep-abr.md) - a WHEP viewer switches renditions from its own congestion feedback - [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - whether bounded drain credit avoids ProbeRTT deadline interference - [Discover media headroom](/quest/m2/quic-probe.md) - test useful-media pacing before adding redundant probe traffic - [L4S on the backbone](/quest/m2/quic-ecn.md) - an ECT(1) option in the fork, an `ecn` config knob, and a dualpi2 measurement - [Careful resume on reconnect](/quest/m2/quic-careful-resume.md) - a redial starts at the previous connection's rate - [Keep-alive by deadline](/quest/m2/quic-keep-alive.md) - a PING only when the idle deadline nears, no fixed timer - [noq socket close](/quest/m2/noq-socket-close.md) - noq releases an endpoint's socket on close, so moq-tokio drops its wrapper -- [Drop the hidden cluster exemption](/quest/m2/hidden-exemption.md) - relays stop forcing hidden broadcasts on cluster peers once every peer opts in on the wire -- [Routing cost domains](/quest/m2/routing-cost-domains.md) - design operator boundaries and policy without adding incomparable costs -- [Kernel pacing](/quest/m2/quic-kernel-pacing.md) - whether SO_TXTIME pacing beats a userspace pacer the io_uring driver ignores today -- [Send batching](/quest/m2/quic-send-batching.md) - whether sendmmsg across connections pays on the tokio path -- [Send buffer pools](/quest/m2/quic-buffer-pool.md) - whether pooled send buffers beat Bytes in the stream send path -- [AF_XDP UDP path](/quest/m2/af-xdp.md) - the kernel-bypass verdict on today's virtio hosts that gates DPDK - [GOP overhead](/quest/m2/gop-overhead.md) - price the I-frames a short GOP pays for, deciding whether a long GOP plus a keyframe request is worth designing - [Per-program SI](/quest/m2/ts-program-si.md) - a selected TS program's broadcast carries only its own service's SDT and EIT - [TS import health](/quest/m2/ts-import-health.md) - `moq import ts` counts the TR 101 290 errors of the feed it receives, PCR and PTS graded on its own values - [TS export liveness](/quest/m2/ts-export-liveness.md) - `moq export ts` reports each elementary stream's access units and quiet time, catching a per-track stall - [TS health stats](/quest/m2/ts-health-stats.md) - the TS counters ride the stats plumbing beside the media counters - [Teleoperation](/quest/m2/teleop/README.md) - MoQ carries robot video down and control up on one session as a library capability -- [SIP media stack](/quest/m2/sip-stack.md) - terminate one inbound SIP audio call leg and expose it as Opus frames -- [Carrier voice](/quest/m2/carrier-voice/README.md) - determine whether MoQ should be the call fabric for programmable carrier voice -- [LiveKit WebRTC bridge](/quest/m2/livekit-webrtc-bridge.md) - a go/no-go verdict, backed by a spike, on per-track LiveKit-to-MoQ bridging -- [Common Access Tokens](/quest/m2/cat/README.md) - a moq-transport client presents a CAT in SETUP and `moq auth serve` admits it with the scope its `moqt` claim names -- [Runtime QA hosts](/quest/m2/runtime-qa-hosts.md) - run exact source snapshots on accessible Linux and device hosts with retrievable debug evidence - [Media QA on other engines](/quest/m2/browser-media-qa-engines.md) - the media harness measures a Firefox or WebKit player over the fallback and names what each engine lacks - [Firefox 155 WebTransport](/quest/m2/firefox-155-webtransport.md) - Firefox negotiates the version by subprotocol, and the other new WebTransport features stay unused on purpose - [Windows capture parity](/quest/m2/capture-windows.md) - system audio and screen cursor capture with a settled app-capture policy - [Linux capture parity](/quest/m2/capture-linux.md) - Wayland window/system-audio capture with explicit display-selection and app-capture limits -- [Plan capture ergonomics](/quest/m2/capture-ergonomics.md) - scope independent crop and audio mixing quests - [Audio capture time](/quest/m2/audio-capture-time.md) - native audio stamps a buffer's capture instant, not when the driver reads it - [X11 capture transport](/quest/m2/x11-capture-shm.md) - move X11 capture to shared memory and RandR events instead of a per-frame socket copy -- [Capture frame buffers](/quest/m2/capture-frame-buffers.md) - stop rebuilding a full-frame buffer every tick in the X11 and Windows backends -- [Demo serve-hls renditions](/quest/m2/serve-hls-renditions.md) - `just pub serve-hls` serves 720p and 144p instead of two 256-wide copies +- [Send batching](/quest/m2/quic-egress-profile.md) - whether sendmmsg across connections pays on the tokio path +- [SEI separation](/quest/m2/sei.md) - retain inline SEI until measured savings or a metadata-only consumer justify a split +- [Compressed tracks](/quest/m2/flate.md) - the hand-written binding wrappers expose flate tracks +- [Announcement shapes](/quest/m2/announce-shapes.md) - moq-lite announcements and interests carry prefix, exact, suffix, or prefix+suffix shapes that survive relay hops, benchmarked over the announce table +- [MSFTS convergence](/quest/m2/msfts-convergence.md) - the demultiplexed TS lane maps onto MSFTS ES-level carriage once msfts#33 settles the payload unit +- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - H.265 encode and decode, and pre-generated bindings that remove the libclang build dependency, gated on a moq-dev/vaapi release diff --git a/quest/m3/announce-shapes.md b/quest/m2/announce-shapes.md similarity index 92% rename from quest/m3/announce-shapes.md rename to quest/m2/announce-shapes.md index 19083a6c45..2bae9d7648 100644 --- a/quest/m3/announce-shapes.md +++ b/quest/m2/announce-shapes.md @@ -31,8 +31,6 @@ Decided 2026-09-29 (quest-plan interview): the version-gated wire approach both shapes need. - The old gate ("a deployment needs a suffix claim a service prefix cannot express") is removed: the exact-scope leak is reason enough. -- [Path patterns](/quest/m1/path-patterns.md) keeps advertisements as prefixes - and names this quest as the owner of non-prefix routing. ### Wire @@ -98,12 +96,10 @@ routes independently of Rust. The slopes decide between a reversed-segment index and dropping suffix shapes from the quest. A non-prefix advertisement needs authorizing: -[advertise auth](/quest/m1/processor/advertise-auth.md) scopes are prefix-only +[advertise auth](/quest/m2/processor/advertise-auth.md) scopes are prefix-only today, and this quest extends them to the new shapes. Token patterns (`moq-pattern`, `moq_auth::Claims`) already match suffixes and do not change. ## Related - [Wildcard](/quest/m0/wildcard/README.md) - prefix-only advertisements and the service-prefix layout this extends -- [Path patterns](/quest/m1/path-patterns.md) - owns the pattern dialect a shaped interest would reuse -- [Cluster routing](/quest/m1/cluster-routing.md) - forwards announcements between relays, which must keep their shape diff --git a/quest/m2/archive-backward-timestamps.md b/quest/m2/archive-backward-timestamps.md deleted file mode 100644 index c5175e4c41..0000000000 --- a/quest/m2/archive-backward-timestamps.md +++ /dev/null @@ -1,16 +0,0 @@ -# [XS] Archive refuses backward timestamps - -## Goal - -A resumed recording refuses a track whose timestamps go backward past the -recovered timeline, failing loud like the group-ID check, instead of writing -overlapping media time. The caller starts a new prefix. - -## Plan - -Check the first group's timestamp against the recovered track's last recorded -timestamp at enrollment, and test both a backward and a forward restart. - -## Required - -- [Archive](/quest/m1/archive/README.md) - the recovery this hardens ships with the line diff --git a/quest/m1/archive/browser.md b/quest/m2/archive-browser.md similarity index 100% rename from quest/m1/archive/browser.md rename to quest/m2/archive-browser.md diff --git a/quest/m1/archive/paced-replay.md b/quest/m2/archive-paced-replay.md similarity index 100% rename from quest/m1/archive/paced-replay.md rename to quest/m2/archive-paced-replay.md diff --git a/quest/m2/audio-decode-mediafoundation.md b/quest/m2/audio-decode-mediafoundation.md index 6fae6be4fa..d4edf77dfe 100644 --- a/quest/m2/audio-decode-mediafoundation.md +++ b/quest/m2/audio-decode-mediafoundation.md @@ -26,8 +26,3 @@ ones. Behind the decode seam as the first candidate on `target_os = - [Decode seam](/quest/m1/audio-codecs/decode-backend.md) - the candidate order this backend joins - [Layout](/quest/m1/audio-codecs/layout.md) - what a multichannel frame is delivered as - -## Related - -- [Runtime QA hosts](/quest/m2/runtime-qa-hosts.md) - where the Windows run happens -- [Windows decoded frames](/quest/m1/obs-moq-video/decode-windows.md) - the OBS Windows line this feeds diff --git a/quest/m2/audio-encode-mediafoundation.md b/quest/m2/audio-encode-mediafoundation.md index b1c3440f6b..6600698e7c 100644 --- a/quest/m2/audio-encode-mediafoundation.md +++ b/quest/m2/audio-encode-mediafoundation.md @@ -23,7 +23,3 @@ behind the encode seam on Windows. - [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - the candidate order this backend joins - [Layout](/quest/m1/audio-codecs/layout.md) - the input layout the encoder accepts - [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - the round-trip regression decodes through it - -## Related - -- [Runtime QA hosts](/quest/m2/runtime-qa-hosts.md) - where the Windows run happens diff --git a/quest/m2/audio-loss-recovery.md b/quest/m2/audio-loss-recovery.md deleted file mode 100644 index 9be8b40dc0..0000000000 --- a/quest/m2/audio-loss-recovery.md +++ /dev/null @@ -1,28 +0,0 @@ -# [M] Decide a tested Opus loss-recovery policy - -## Goal - -Decide whether a concrete MoQ audio consumer benefits from in-band Opus FEC, -with a tested loss/latency policy before exposing any additive option on -`encode::Settings`. -This does not block 0.1. - -## Plan - -The old boolean supplied no expected-loss percentage and our decoder never -requested FEC recovery. Treat source loss, late-group abandonment, and DTX as -different cases. Define sequencing, expected loss, playout lookahead, recovery -versus concealment, and behavior when the next packet is unavailable. - -Use deterministic dropped-packet fixtures to prove redundancy is emitted and -used, compare audible quality and delay against concealment, and name the -transport scenario where it helps. End with a measured go/no-go and a small -additive policy if justified; do not reintroduce an enable flag tested only by -reading the codec's control value. Implementation fixtures belong in CI. - -Public API: no change during the study; any later policy must fit the extensible -audio settings. Existing wire compatibility must be demonstrated. - -## Related - -- [Audio quality](/quest/m0/audio-quality-harness/README.md) - quality and latency measurements diff --git a/quest/m2/av1-metadata.md b/quest/m2/av1-metadata.md index 475794e8de..996a293290 100644 --- a/quest/m2/av1-metadata.md +++ b/quest/m2/av1-metadata.md @@ -28,13 +28,3 @@ not. Preserve `metadata_type`, the payload bytes, and ordering within the temporal unit. Test HDR10+ T.35 metadata, timecode, an unknown metadata type, several OBUs in one temporal unit, and a byte-identical round trip. - -## Required - -- [Metadata association contract](/quest/m2/sei/sei.md) - settles the shared framing and missing-data semantics before this section adopts them - -## Related - -- [SEI sidecars](/quest/m2/sei/README.md) - the H.26x contract this should - follow rather than duplicate -- [fMP4 emsg carriage](/quest/m1/emsg.md) - independent carriage of container metadata, not a prerequisite for codec extraction diff --git a/quest/m1/bench-coverage.md b/quest/m2/bench-coverage.md similarity index 100% rename from quest/m1/bench-coverage.md rename to quest/m2/bench-coverage.md diff --git a/quest/m2/browser-media-qa-engines.md b/quest/m2/browser-media-qa-engines.md index 343d8cb478..fce4bce8af 100644 --- a/quest/m2/browser-media-qa-engines.md +++ b/quest/m2/browser-media-qa-engines.md @@ -31,7 +31,3 @@ which is the path a real Firefox viewer takes today. a measurement of that engine, not of the player. - Playwright WebKit is not Safari. Say so in the report; a Safari defect such as #2812 still needs a manual run. - -## Related - -- [Runtime QA hosts](/quest/m2/runtime-qa-hosts.md) - where an engine that will not run on the CI image runs diff --git a/quest/m1/perf/cache-shard.md b/quest/m2/cache-shard.md similarity index 100% rename from quest/m1/perf/cache-shard.md rename to quest/m2/cache-shard.md diff --git a/quest/m1/captions-cea.md b/quest/m2/captions-cea.md similarity index 89% rename from quest/m1/captions-cea.md rename to quest/m2/captions-cea.md index 909fa7e97b..f352869c12 100644 --- a/quest/m1/captions-cea.md +++ b/quest/m2/captions-cea.md @@ -31,9 +31,3 @@ Whichever lands first, the import order is a contract, not an accident: caption extraction sees the SEI before stripping removes it. Running the split first silently produces a broadcast with no captions, which is the failure this quest exists to prevent, so cover the ordering with a test rather than a comment. - -## Related - -- [SEI](/quest/m2/sei/README.md) - carries SEI byte-faithfully as a sidecar; if - that line lands, this parser is a candidate to move onto it rather than - walking the access unit itself diff --git a/quest/m2/capture-ergonomics.md b/quest/m2/capture-ergonomics.md deleted file mode 100644 index 0584416ba7..0000000000 --- a/quest/m2/capture-ergonomics.md +++ /dev/null @@ -1,27 +0,0 @@ -# [S] Plan capture ergonomics - -## Goal - -Two papercuts in the capture surface, none blocking but each visible the -first time someone hits it. - -This is a planning dispatch. Verify each current limitation against source, -then replace this quest with independently completable crop and audio mixing -quests. Each needs a chosen public API, ownership -boundary, supported/refused cases, and CI acceptance tests. Ask the maintainer -about unsettled crop coordinates and audio clock/mixing policy before coding. -Keep additions compatible with the settled main capture contracts. Identify any -published API break for a separate maintainer decision; independent format -validation already has its own quest. - -## Plan - -- **Region and crop capture.** No knob exists anywhere; `capture::Config` - carries source, device, width, height, and framerate. Cropping a region of a - display is the common screen-share case that currently requires capturing - the whole thing. -- **Mixing multiple audio devices.** One device, one track. A screen share - wanting microphone plus system audio has no way to say so, which is exactly - the combination the `System` source makes newly reachable. - Preserve exclusive AEC microphone ownership and define clock alignment - before sharing processed microphone input. diff --git a/quest/m2/capture-frame-buffers.md b/quest/m2/capture-frame-buffers.md deleted file mode 100644 index 156a3e5fe8..0000000000 --- a/quest/m2/capture-frame-buffers.md +++ /dev/null @@ -1,30 +0,0 @@ -# [S] moq-video: X11 and Windows capture rebuild their frame buffers every tick - -## Goal - -Neither native screen-capture backend allocates a full-frame buffer per frame. -Steady-state capture reuses the same scratch memory and GDI objects. - -## Plan - -Both backends added in the native screen-capture work allocate the whole frame, -every frame, on the pump thread. - -`capture/window.rs`'s `snapshot` creates a memory DC, a compatible bitmap, and a -`vec![0u8; w * h * 4]` per call, then destroys them. At 1080p60 that is roughly -500 MB/s of allocation plus GDI object churn for pixels whose size never changes -while the stream is open. All three belong in `Capture`, built once at open: the -pump thread owns the struct, so the `!Send` handles are fine where they are. - -`capture/x11.rs`'s `PixelFormat::rgb` allocates a `w * h * 3` `Vec` and fills it -with three `push` calls per pixel, and `I420::from_rgb` then walks it again into -a third buffer. Take an `&mut Vec` the `Capture` owns and `clear()` it, so -the allocation happens once. - -Neither is a correctness bug, so this is a steady-state cost question: measure -before and after rather than assuming. The X11 half compiles on Linux CI; the -Windows half needs a Windows host (`just rs windows`). - -## Related - -- [X11 capture transport](/quest/m2/x11-capture-shm.md) - the larger X11 cost, in the same read path diff --git a/quest/m2/carrier-voice/README.md b/quest/m2/carrier-voice/README.md deleted file mode 100644 index 4be5cad6d1..0000000000 --- a/quest/m2/carrier-voice/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Programmable carrier voice - -## Goal - -Determine whether MoQ should be the programmable call fabric between developer -clients, carrier gateways, and auxiliary call services. The design covers -developer-to-gateway and gateway-to-gateway calls; the first lab proves only a -developer client calling a SIP endpoint. - -Stock phones remain on IMS/SIP/RTP. MoQ begins at a carrier-controlled gateway -or developer client, where publish/subscribe can make a live call available to -recorders, agents, transcription, translation, and conferencing without a -bespoke media fork for each service. - -## Plan - -- Use one role-based call model for both topologies. A developer client and a - SIP/IMS gateway are authenticated call legs; internal gateway-to-gateway - transport is the same protocol with two gateway legs, not a second design. -- Calling is an application protocol carried by ordinary - [MOQT](https://datatracker.ietf.org/doc/draft-ietf-moq-transport/). A leg - publishes a short-lived offer and call-state objects plus its Opus audio; the - other leg subscribes and publishes its own state and audio. - `PUBLISH_NAMESPACE` and `SUBSCRIBE_NAMESPACE` provide discovery only. Do not - add call semantics to MOQT or treat namespace publication itself as ringing. -- Scope offers below an opaque line and random call id, never a raw phone - number or caller-asserted identity. The E.164 destination is authenticated - offer metadata, the usable source line comes from the credential, and the - gateway is authoritative for routing and telephone state. -- Offers are live session state, not an offline inbox. Withdrawing the offer or - losing its publisher cancels an unanswered call. Durable notifications, - voicemail, retries after disconnect, and webhook delivery remain separate - product surfaces. -- Keep IMS registration, native handset integration, roaming, emergency - calling, lawful intercept, number provisioning, SMS/MMS/RCS, and carrier - compliance outside the experiment. The SIP adapter is the boundary to that - world. - -## Required - -- [Call fabric protocol](/quest/m2/carrier-voice/protocol.md) - versioned - namespaces, roles, state transitions, authorization, and both topologies -- [SIP call origination](/quest/m2/carrier-voice/sip-originate.md) - the shared - SIP stack originates one outbound audio call for the lab -- [Developer-to-SIP proof](/quest/m2/carrier-voice/proof.md) - a developer - publishes a call that reaches a SIP endpoint, with a passive second consumer -- [Carrier-voice verdict](/quest/m2/carrier-voice/verdict.md) - compare MoQ, - RTP, and RoQ, then record the smallest justified product surface - -## Related - -- [SIP media stack](/quest/m2/sip-stack.md) - the telephone-network adapter - this lab extends; the inbound-call product built on it is moq.pro - (downstream) work -- `@moq/room` - conferencing may eventually reuse its participant model, but is - not required by this experiment diff --git a/quest/m2/carrier-voice/proof.md b/quest/m2/carrier-voice/proof.md deleted file mode 100644 index df3b8b7f90..0000000000 --- a/quest/m2/carrier-voice/proof.md +++ /dev/null @@ -1,35 +0,0 @@ -# [L] Developer-to-SIP proof - -## Goal - -A reproducible lab call starts when a developer client publishes a MoQ offer, -rings and answers at a real SIP endpoint, and carries bidirectional Opus audio. -A recorder or mock agent subscribes to the live call without either endpoint -creating a separate media connection. - -## Plan - -- Build the smallest standalone gateway beside the protocol and `moq-sip`: - subscribe to the configured line prefix, authorize the offer, originate the - SIP dialog, publish authoritative call state and received audio, then - subscribe to the developer leg and send that audio as RTP. Do not wire the - experiment into the moq.pro (downstream) edge or dashboard. -- Drive it with a Rust or TypeScript developer client and a scripted SIP - endpoint or interoperable softphone. One command starts the relay, gateway, - endpoint, client, and auxiliary subscriber; retained artifacts include state - transitions, synchronized audio timestamps, and packet captures. -- Prove happy-path ringing/answer/audio and the failure paths that define the - contract: rejection, caller cancellation, remote hangup, publisher loss, - unauthorized line use, spoofed source identity, and an auxiliary subscriber - without recording permission. -- Keep the second consumer passive so the proof measures MoQ's programmable - fanout rather than designing conferencing. The gateway-to-gateway topology - receives an architecture-level walkthrough using the same roles and wire, - but no second gateway implementation. - -## Required - -- [Call fabric protocol](/quest/m2/carrier-voice/protocol.md) - fixes the wire, - authority, and lifecycle the lab implements -- [SIP call origination](/quest/m2/carrier-voice/sip-originate.md) - supplies - the outbound telephone leg diff --git a/quest/m2/carrier-voice/protocol.md b/quest/m2/carrier-voice/protocol.md deleted file mode 100644 index 1ea89aaa43..0000000000 --- a/quest/m2/carrier-voice/protocol.md +++ /dev/null @@ -1,44 +0,0 @@ -# [M] Call fabric protocol - -## Goal - -A versioned experimental protocol defines how authenticated call legs offer, -accept, carry, and end an audio call over ordinary MoQ. It describes both -developer-to-gateway and gateway-to-gateway calls without exposing phone -numbers in namespaces or trusting caller-supplied identity. - -## Plan - -- Define a project-scoped line inbox and opaque call/leg hierarchy. The shape - starts at `voice/lines//calls//legs/`; each leg - publishes only its own broadcast, containing an Opus audio track and a - versioned state track. The exact wire lives beside the Rust and TypeScript - implementations, not only in this quest. -- The first caller-leg state is an offer. For outbound telephone calls it - carries the E.164 destination and requested capabilities. Gateway-authored - states report routing, ringing, answer, rejection, and termination. Define - ordering, duplicate/replay handling, terminal-state behavior, cancellation - on withdrawal, and simultaneous teardown so reconnects cannot resurrect a - call. -- Authorization assigns roles rather than trusting path text: a caller may - publish its leg and request use of a configured line, the carrier gateway - validates that line and publishes authoritative telephone state, the remote - leg publishes its media, and an auxiliary consumer receives a least-privilege - subscription to the call. A source number is derived from the credential and - line configuration, never accepted from offer metadata. -- Use standard namespace discovery to notice new calls under an authorized - line prefix. Do not extend MOQT. Bound offer lifetime, state-object size, - call count, and per-line concurrency, and specify what is observable to a - relay even when media objects are encrypted. -- Include sequence diagrams for developer-to-SIP and gateway-to-gateway calls, - plus a threat model covering number enumeration, caller-ID spoofing, path - injection, unauthorized recording, replay, abandoned offers, and confused - deputy use of a carrier trunk. -- Land shared schema/types and state-machine tests in this repository. Keep - HTTP/webhooks out of the wire; the verdict may recommend them later as a - durable product-control surface without changing the live media model. - -## Related - -- [SIP call origination](/quest/m2/carrier-voice/sip-originate.md) - adapts the - telephone leg to the protocol diff --git a/quest/m2/carrier-voice/sip-originate.md b/quest/m2/carrier-voice/sip-originate.md deleted file mode 100644 index c973fbbac2..0000000000 --- a/quest/m2/carrier-voice/sip-originate.md +++ /dev/null @@ -1,32 +0,0 @@ -# [M] SIP call origination - -## Goal - -The `moq-sip` stack can originate one audio-only SIP call and expose the -resulting dialog as Opus frames in and out. The first consumer is the -programmable carrier-voice lab, not a production outbound-calling product. - -## Plan - -- Extend the selected SIP stack rather than building a second SIP adapter. The - embedder supplies the request URI, asserted line identity, credentials, and - Opus source/sink; the crate owns INVITE transactions, SDP offer/answer, - provisional and final responses, RTP/SRTP, cancellation, and BYE. -- Reuse the inbound stack's Opus/G.711 negotiation, transcoding, RTP clock - normalization, and teardown. Exercise ringing, answer, rejection, caller - cancellation, remote hangup, and timeout against a real softphone or test - PBX. -- Keep PSTN routing, number ownership, caller-ID policy, emergency calling, - registrar support, DTMF, billing, and production trunk credentials outside - this quest. The lab gateway decides whether an authenticated caller may use - a line before it asks `moq-sip` to originate. - -## Required - -- [SIP media stack](/quest/m2/sip-stack.md) - origination extends the same - dialog, codec, and RTP implementation - -## Related - -- [Developer-to-SIP proof](/quest/m2/carrier-voice/proof.md) - the first - end-to-end consumer diff --git a/quest/m2/carrier-voice/verdict.md b/quest/m2/carrier-voice/verdict.md deleted file mode 100644 index f80046a187..0000000000 --- a/quest/m2/carrier-voice/verdict.md +++ /dev/null @@ -1,44 +0,0 @@ -# [M] Carrier-voice verdict - -## Goal - -A measured go/no-go verdict decides whether MoQ earns a maintained role in -programmable carrier voice, and if so whether that role is developer-facing, -an internal gateway fabric, or both. An inconclusive result creates no product -commitment. - -## Plan - -- Replay the same Opus source through direct RTP, the MoQ lab path, and - [RTP over QUIC](https://datatracker.ietf.org/doc/draft-ietf-avtcore-rtp-over-quic/) - (RoQ). Its current Internet-Draft is expired prior art, not an assumed - adoption target: use a credible implementation if one remains runnable; - otherwise record why that arm cannot support a product decision instead of - building a second production stack just for the comparison. -- Use fixed clean, constrained-cellular, random-loss, burst-loss, and simulated - Wi-Fi/cellular path-change profiles. Record call setup time, one-way audio - latency and jitter, late/lost frames, recovery gaps, wire overhead, gateway - CPU, and connection continuity. Keep codec, packetization, source audio, - network trace, and hardware fixed across arms. -- Measure the proposed advantage directly: attach the auxiliary subscriber and - report its incremental setup work, latency, bandwidth, and gateway CPU. Also - document the equivalent RTP media-fork implementation so "programmable" is - compared against a real alternative rather than asserted. -- A go requires the full call lifecycle and mobility profiles to complete - without a protocol workaround, and MoQ's p99 one-way latency to stay within - 50 ms of direct RTP while the auxiliary subscriber remains an ordinary - authorized subscription. Treat the threshold as a rejection bound, not a - claim that a 50 ms regression is desirable. -- The verdict separately answers: whether in-band MoQ call control is simpler - than HTTP plus media streams, whether QUIC path migration helps in the - implementations we can actually ship, whether object overhead is acceptable - for 20 ms audio, and whether gateway-to-gateway transport adds value beyond - the developer-facing API. Recommend only the smallest surface supported by - the evidence and create implementation quests only for that surface. -- The conventional inbound SIP gateway product is moq.pro (downstream) work - and remains the standing telephone path regardless of this verdict. - -## Required - -- [Developer-to-SIP proof](/quest/m2/carrier-voice/proof.md) - provides the - working MoQ path and reproducible harness diff --git a/quest/m1/closure-counters.md b/quest/m2/closure-counters.md similarity index 100% rename from quest/m1/closure-counters.md rename to quest/m2/closure-counters.md diff --git a/quest/m2/cpp-vcpkg.md b/quest/m2/cpp-vcpkg.md index 36ffc7427f..33f026e32b 100644 --- a/quest/m2/cpp-vcpkg.md +++ b/quest/m2/cpp-vcpkg.md @@ -27,7 +27,3 @@ CI on Windows, macOS, and Linux. ## Required - [Package](/quest/m1/cpp/package.md) - the release tarballs the port fetches - -## Related - -- [Conan remote](/quest/m2/cpp-conan.md) - the same tarball through Conan diff --git a/quest/m1/emsg.md b/quest/m2/emsg.md similarity index 95% rename from quest/m1/emsg.md rename to quest/m2/emsg.md index 505d9c3f7b..a9ceb774ba 100644 --- a/quest/m1/emsg.md +++ b/quest/m2/emsg.md @@ -55,8 +55,5 @@ byte-identical. ## Related -- [ID3 catalog section](/quest/m1/id3.md) - gives one payload type carried here a - typed contract - [AV1 metadata OBUs](/quest/m2/av1-metadata.md) - the same silent drop in a different layer -- [FLV script tags](/quest/m1/flv-script.md) - likewise diff --git a/quest/m2/flate/README.md b/quest/m2/flate.md similarity index 88% rename from quest/m2/flate/README.md rename to quest/m2/flate.md index abb54d689d..41b3b3cba0 100644 --- a/quest/m2/flate/README.md +++ b/quest/m2/flate.md @@ -16,7 +16,3 @@ remains is reaching those tracks from the hand-written binding wrappers. No wire, catalog, or relay impact. Compression stays invisible to `moq-net`; a compressed track is announced, routed, and cached like any other. - -## Required - -- [Bindings](/quest/m2/flate/bindings.md) - the hand-written wrappers expose flate tracks diff --git a/quest/m2/flate/bindings.md b/quest/m2/flate/bindings.md deleted file mode 100644 index bf6f8042b9..0000000000 --- a/quest/m2/flate/bindings.md +++ /dev/null @@ -1,32 +0,0 @@ -# [M] Flate bindings - -## Goal - -The hand-written wrappers (Python, Swift, Kotlin, Go, Dart) expose flate -tracks, the snapshot and stream opaque tracks moq-ffi already generates, in -their own idiom beside the JSON entry. A track published from a wrapper -decodes in the browser with `@moq/flate` and vice versa. - -## Plan - -moq-ffi publishes opaque tracks today (`publish_binary_snapshot` and -`publish_binary_stream`, #4137), renamed after `flate` by -moq-binary's fold into moq-flate ([#4425](https://github.com/moq-dev/moq/pull/4425), on `dev`). Only the -generated bindings reach them; no wrapper does. This quest binds the existing -track modes, not the bare codec: a `frame()` call across the FFI boundary -invites the window desync the track modes exist to prevent. - -- moq-ffi has no consume side for these tracks. Add it next to the JSON - consumers so each wrapper can read what it writes. -- Wrappers per the Cross-Package Sync table: `go/wrapper/json.go`, - `py/moq-rs/moq/{publish,subscribe}.py`, `swift/Sources/Moq/Json.swift`, - `kt/moq`'s `Json.kt` with its `Aliases.kt` re-exports, and - `dart/moq/lib/src/aliases.dart` each gain a flate sibling. If - [FFI shape](/quest/m1/ffi-shape/README.md) has landed, follow its `flate` - namespace instead. -- Document in `doc/lib/{py,swift,kt,go,dart}` beside the JSON entry. -- Tests: a round trip in each wrapper that has tests, and one cross-language - check that a wrapper-published group decodes with `@moq/flate`. Run - `just test interop --all`. - -Public API: additive on moq-ffi and every wrapper. Wire: none. diff --git a/quest/m1/flv-script.md b/quest/m2/flv-script.md similarity index 82% rename from quest/m1/flv-script.md rename to quest/m2/flv-script.md index 27dc2919cd..9af5457ca4 100644 --- a/quest/m1/flv-script.md +++ b/quest/m2/flv-script.md @@ -15,7 +15,7 @@ through the same channel. Carry raw tag payloads without decoding AMF into a fixed vocabulary. Publish when received on independently sequenced metadata groups, with event time on the broadcast clock and source placement per the contract -[emsg](/quest/m1/emsg.md) settles. Tags before media +[emsg](/quest/m2/emsg.md) settles. Tags before media are delivered immediately and retain explicit pre-media placement; audio-only and script-only input do not require a dummy video rendition. Where `onMetaData` duplicates something the catalog already models (dimensions, @@ -26,12 +26,6 @@ Export reproduces the tags on the FLV path. Test `onMetaData`, an application message with a custom name, AMF0 and AMF3 payloads, a tag before the first media tag, and a byte-identical round trip. -## Required - -- [fMP4 emsg](/quest/m1/emsg.md) - settles the shared framing and missing-data semantics before this section adopts them - ## Related -- [fMP4 emsg carriage](/quest/m1/emsg.md) - the same silent drop in a different - layer - [AV1 metadata OBUs](/quest/m2/av1-metadata.md) - likewise diff --git a/quest/m1/id3.md b/quest/m2/id3.md similarity index 85% rename from quest/m1/id3.md rename to quest/m2/id3.md index 4d5f1d019f..e952b0d5d1 100644 --- a/quest/m1/id3.md +++ b/quest/m2/id3.md @@ -11,7 +11,7 @@ stream. ## Plan Define the catalog section and frame contract for complete ID3 tags on the -shared event contract [emsg](/quest/m1/emsg.md) settles. Publish tags when +shared event contract [emsg](/quest/m2/emsg.md) settles. Publish tags when received on independently sequenced groups, with their presentation time on the broadcast clock and an optional association to the program's rendition. ID3-only programs need no artificial media owner. Carry original tag bytes, @@ -34,12 +34,3 @@ with default and non-default PMT descriptors, multiple tags, unknown frames, large tags spanning PES packets, timestamp wrap, discontinuity, and an ID3-only program. Include non-ID3 and malformed stream type `0x15` fixtures that remain generic. - -## Required - -- [fMP4 emsg](/quest/m1/emsg.md) - settles the shared framing and missing-data semantics before this section adopts them - -## Related - -- [SEI sidecars](/quest/m2/sei/README.md) - the separate codec metadata - contract diff --git a/quest/m2/intra-refresh/README.md b/quest/m2/intra-refresh/README.md index ca1405bcac..cd3343c60a 100644 --- a/quest/m2/intra-refresh/README.md +++ b/quest/m2/intra-refresh/README.md @@ -41,10 +41,6 @@ Decisions the quests share: - [Consumer warmup](/quest/m2/intra-refresh/consumer-warmup.md) - JS and Rust viewers join `warmup` earlier and withhold display until recovery, except at a true IDR - [H.264 import](/quest/m2/intra-refresh/h264-import.md) - the splitter keeps `recovery_frame_cnt` and import publishes `warmup` from it - [H.265 import](/quest/m2/intra-refresh/h265-import.md) - the splitter reads the recovery-point SEI so an HEVC intra-refresh stream forms groups and publishes `warmup` -- [Encode config](/quest/m2/intra-refresh/encode-config.md) - refresh mode extends the settled GOP contract; the producer cuts groups per sweep and publishes `warmup` -- [NVENC refresh](/quest/m2/intra-refresh/nvenc-refresh.md) - the NVENC backend encodes refresh mode for H.264 and HEVC -- [V4L2 refresh](/quest/m2/intra-refresh/v4l2-refresh.md) - the V4L2 backend encodes refresh mode -- [Bindings](/quest/m2/intra-refresh/bindings.md) - moq-ffi and every wrapper expose refresh mode, additive on the ffi-shape `Gop` enum - [Export sync flags](/quest/m2/intra-refresh/export-sync-flags.md) - fmp4, MKV, and HLS stop advertising a refresh group start as a sync sample ## Related diff --git a/quest/m2/js-discontinuity.md b/quest/m2/js-discontinuity.md deleted file mode 100644 index 0270c141b2..0000000000 --- a/quest/m2/js-discontinuity.md +++ /dev/null @@ -1,22 +0,0 @@ -# [XS] JS discontinuity() writes no estimated end - -## Goal - -`@moq/hang`'s `discontinuity()` without an explicit end closes the group with -no cadence-estimated end, as Rust `moq_mux::container::Producer::discontinuity` -does. Whatever resumes can land sooner than one estimated frame later (a -capture swap), and an end past it reads as a rewind to every consumer. - -## Plan - -The rename landed on `dev` in #4141 and kept the old behavior: in -`js/hang/src/container/legacy.ts`, `discontinuity(end?)` calls `#close(end)`, -which fills a missing `end` with `#end + #interval`. Rust's `discontinuity()` -calls `close(None, None)` and clears the cadence first. - -Give the break its own close path that passes no estimate when the caller -gave no end, leaving the routine close's estimate alone. Test that a -discontinuity after a steady cadence writes no end past the last frame. - -Public API: behavior change in `@moq/hang`'s `discontinuity()`, which exists -only on `dev`, so it targets `dev`. Wire: none. diff --git a/quest/m1/ladder/README.md b/quest/m2/ladder/README.md similarity index 96% rename from quest/m1/ladder/README.md rename to quest/m2/ladder/README.md index 0c242cc396..3b1867c2b5 100644 --- a/quest/m1/ladder/README.md +++ b/quest/m2/ladder/README.md @@ -73,9 +73,9 @@ encoders on every target change. ## Required -- [Controller](/quest/m1/ladder/controller.md) - one controller owns every +- [Controller](/quest/m2/ladder/controller.md) - one controller owns every rung's share, target, stalled state, and send order -- [Fetch and catalog](/quest/m1/ladder/fetch.md) - uncached FETCH encodes at +- [Fetch and catalog](/quest/m2/ladder/fetch.md) - uncached FETCH encodes at the shared applied target, and rung state survives a source catalog refresh ## Closes diff --git a/quest/m1/ladder/controller.md b/quest/m2/ladder/controller.md similarity index 98% rename from quest/m1/ladder/controller.md rename to quest/m2/ladder/controller.md index 32ba4c1860..53015fe217 100644 --- a/quest/m1/ladder/controller.md +++ b/quest/m2/ladder/controller.md @@ -18,7 +18,7 @@ today's fixed-rate behavior exactly and never publishes congestion-induced `stalled` state, which is what keeps this additive. The controller subdivides that estimate across the ladder and applies the -band boundary from the [questline](/quest/m1/ladder/README.md), including the +band boundary from the [questline](/quest/m2/ladder/README.md), including the lowest rung's `max / 3` case. `moq_transcode::Ladder` is ascending: `new` sorts by configured maximum (`rs/moq-transcode/src/ladder.rs:101`) and `rungs()` is lowest first (`:125-128`), so the next lower rendition the diff --git a/quest/m1/ladder/fetch.md b/quest/m2/ladder/fetch.md similarity index 95% rename from quest/m1/ladder/fetch.md rename to quest/m2/ladder/fetch.md index 54630fedce..4e529ecc2a 100644 --- a/quest/m1/ladder/fetch.md +++ b/quest/m2/ladder/fetch.md @@ -31,5 +31,5 @@ intact. ## Required -- [Controller](/quest/m1/ladder/controller.md) - there is no shared applied +- [Controller](/quest/m2/ladder/controller.md) - there is no shared applied target to read until the controller owns one diff --git a/quest/m2/livekit-webrtc-bridge.md b/quest/m2/livekit-webrtc-bridge.md deleted file mode 100644 index 3cc4f54ed7..0000000000 --- a/quest/m2/livekit-webrtc-bridge.md +++ /dev/null @@ -1,27 +0,0 @@ -# [M] LiveKit WebRTC bridge evaluation - -## Goal - -A go/no-go verdict, backed by a working spike, on bridging LiveKit rooms to -MoQ per-track over WebRTC using LiveKit's Rust SDK: a native participant -joins the room and republishes its tracks into MoQ, optionally the reverse. -The verdict decides whether this becomes a maintained gateway. - -## Plan - -- The zero-code paths already exist and bound the value: LiveKit Egress - pushes RTMP/SRT into the existing gateways today (composited and - transcoded), and `moq export rtc --connect` publishes WHIP into LiveKit - Ingress. LiveKit has no WHIP/WHEP egress, so per-track LiveKit to MoQ - requires joining the room as a participant via livekit rust-sdks - (Apache 2.0, no Chrome/GStreamer). -- Spike it in-tree: a rust-sdks participant subscribes to all tracks and - republishes via moq-net + moq-mux, with codec passthrough where possible - (Opus/VP8/VP9; H.264 through the Annex-B importer). -- Assess dependency weight of the webrtc stack, per-track fidelity versus the - Egress paths, simulcast handling, and where a real gateway would live (edge - versus standalone). - -## Related - -- [LiveKit client shim](/quest/m2/livekit-shim.md) diff --git a/quest/m2/mobile-capture-android.md b/quest/m2/mobile-capture-android.md index 0c69542352..1cdd5cbe29 100644 --- a/quest/m2/mobile-capture-android.md +++ b/quest/m2/mobile-capture-android.md @@ -21,10 +21,6 @@ decides whether XL is worth spending. `moq-tokio` already reaches into Android through JNI for `tls::init_android`, so the mechanism exists. -## Required - -- [Ownership boundary](/quest/m2/mobile-ownership.md) - decides whether an NDK/JNI backend family is worth building - ## Related - [iOS capture](/quest/m2/mobile-capture-ios.md) - the other half of mobile, which diff --git a/quest/m2/mobile-capture-ios.md b/quest/m2/mobile-capture-ios.md index 968dfc596d..011835b65d 100644 --- a/quest/m2/mobile-capture-ios.md +++ b/quest/m2/mobile-capture-ios.md @@ -22,10 +22,6 @@ Reuse the `capture::Source` shape the other platforms use rather than growing an iOS-specific entry point, so device enumeration and selection behave the same everywhere. -## Required - -- [Ownership boundary](/quest/m2/mobile-ownership.md) - decides whether Rust owns capture on mobile at all - ## Related - [Android capture and encode](/quest/m2/mobile-capture-android.md) - the other half of diff --git a/quest/m2/mobile-completion.md b/quest/m2/mobile-completion.md index 7770a0280f..ede55d9f7a 100644 --- a/quest/m2/mobile-completion.md +++ b/quest/m2/mobile-completion.md @@ -24,7 +24,6 @@ Do not close #700 merely because its next subset or a design decision finished. - [Dart on iOS](/quest/m1/dart-ios.md) - the Dart iOS asset proof - [Dart codec parity](/quest/m1/dart-codecs.md) - codec-enabled artifacts and Dart video consumer integration -- [Mobile ownership](/quest/m2/mobile-ownership.md) - select and scope the mobile media architecture - [iOS capture](/quest/m2/mobile-capture-ios.md) - deliver the selected iOS capture path - [Android capture](/quest/m2/mobile-capture-android.md) - deliver the selected Android capture and codec path diff --git a/quest/m1/moq-install-url.md b/quest/m2/moq-install-url.md similarity index 91% rename from quest/m1/moq-install-url.md rename to quest/m2/moq-install-url.md index 86a1378ba9..9a9eadef2e 100644 --- a/quest/m1/moq-install-url.md +++ b/quest/m2/moq-install-url.md @@ -27,8 +27,3 @@ maintaining another copy of the install logic. working. Keep upgrade and version-selection examples aligned with the canonical installer's interface. Add route coverage to the site's normal checks for the shell response/redirect and error behavior. - -## Required - -- [Install moq](/quest/m1/moq-installer.md) - canonical installer, release - selection, upgrade behavior, and tests are published first diff --git a/quest/m1/moq-installer.md b/quest/m2/moq-installer.md similarity index 94% rename from quest/m1/moq-installer.md rename to quest/m2/moq-installer.md index 78b023d514..d5d5cfa7df 100644 --- a/quest/m1/moq-installer.md +++ b/quest/m2/moq-installer.md @@ -78,7 +78,3 @@ service setup, Windows support, or new release targets. - [Binary release workflow](/quest/m1/tooling/release-binary.md) - reuse its artifacts without requiring workflow consolidation -- [`moq relay`](/quest/m1/moq-relay-subcommand.md) - relay functionality joins - the same executable independently of its installation method -- [Install URL](/quest/m1/moq-install-url.md) - exposes this installer through - the moq.dev website after it is published diff --git a/quest/m1/moq-relay-subcommand.md b/quest/m2/moq-relay-subcommand.md similarity index 100% rename from quest/m1/moq-relay-subcommand.md rename to quest/m2/moq-relay-subcommand.md diff --git a/quest/m4/msfts-convergence.md b/quest/m2/msfts-convergence.md similarity index 100% rename from quest/m4/msfts-convergence.md rename to quest/m2/msfts-convergence.md diff --git a/quest/m1/obs-moq-video/decode-linux.md b/quest/m2/obs-decode-linux.md similarity index 89% rename from quest/m1/obs-moq-video/decode-linux.md rename to quest/m2/obs-decode-linux.md index 8ee602d686..e9ef5a7aac 100644 --- a/quest/m1/obs-moq-video/decode-linux.md +++ b/quest/m2/obs-decode-linux.md @@ -14,7 +14,3 @@ At least one supported Linux hardware decoder delivers frames to OBS without CPU ## Required - [Video source replacement](/quest/m1/obs-moq-video/source.md) - native frame contract and fallback lifecycle - -## Related - -- [VAAPI encode and decode](/quest/m4/video-vaapi.md) - owns remaining VAAPI decode and native surface gaps; avoid a duplicate backend implementation diff --git a/quest/m1/obs-moq-video/decode-windows.md b/quest/m2/obs-decode-windows.md similarity index 100% rename from quest/m1/obs-moq-video/decode-windows.md rename to quest/m2/obs-decode-windows.md diff --git a/quest/m1/obs-moq-video/macos.md b/quest/m2/obs-macos.md similarity index 100% rename from quest/m1/obs-moq-video/macos.md rename to quest/m2/obs-macos.md diff --git a/quest/m1/obs-moq-video/windows.md b/quest/m2/obs-windows.md similarity index 100% rename from quest/m1/obs-moq-video/windows.md rename to quest/m2/obs-windows.md diff --git a/quest/m1/one-port/README.md b/quest/m2/one-port/README.md similarity index 90% rename from quest/m1/one-port/README.md rename to quest/m2/one-port/README.md index 8e9fbe8e00..995ece2737 100644 --- a/quest/m1/one-port/README.md +++ b/quest/m2/one-port/README.md @@ -73,11 +73,6 @@ pre-accepted streams can stand behind. ## Required -- [UDP demux](/quest/m1/one-port/udp-demux.md) - one socket carries QUIC, STUN answers, and the WebRTC media path, with greasing off -- [TCP acceptor](/quest/m1/one-port/tcp-demux.md) - one listener carries TLS-terminated HTTP, RTMP, and RTMPS -- [SRT on the shared socket](/quest/m1/one-port/srt-demux.md) - srt-tokio accepts a virtual socket and the flow table pins its 4-tuples - -## Related - -- [P2P](/quest/m1/p2p/README.md) - the client that names the relay as its STUN server -- [Stream sessions](/quest/m1/uring-tcp/README.md) - the io_uring workers that would host the same demux later +- [UDP demux](/quest/m2/one-port/udp-demux.md) - one socket carries QUIC, STUN answers, and the WebRTC media path, with greasing off +- [TCP acceptor](/quest/m2/one-port/tcp-demux.md) - one listener carries TLS-terminated HTTP, RTMP, and RTMPS +- [SRT on the shared socket](/quest/m2/one-port/srt-demux.md) - srt-tokio accepts a virtual socket and the flow table pins its 4-tuples diff --git a/quest/m1/one-port/srt-demux.md b/quest/m2/one-port/srt-demux.md similarity index 94% rename from quest/m1/one-port/srt-demux.md rename to quest/m2/one-port/srt-demux.md index 92b3da2d15..d12a492083 100644 --- a/quest/m1/one-port/srt-demux.md +++ b/quest/m2/one-port/srt-demux.md @@ -16,7 +16,7 @@ socket. Fallback if refused: `srt-protocol` is sans-io, so `moq-srt` drives its `Listen` and `Connection` state machines directly on fed packets, which is more code but removes the dependency on `srt-tokio`'s socket handling. -The demux side is the flow table from [UDP demux](/quest/m1/one-port/udp-demux.md): +The demux side is the flow table from [UDP demux](/quest/m2/one-port/udp-demux.md): a 4-tuple already pinned to WebRTC (ICE succeeded) is never tested for SRT. An unknown 4-tuple whose full SRT handshake header matches (control bit, type 0, and the SRT magic, not merely `80 00`) pins to SRT provisionally: @@ -35,5 +35,5 @@ with a QUIC client active on the same port; a QUIC short header from a new ## Required -- [UDP demux](/quest/m1/one-port/udp-demux.md) +- [UDP demux](/quest/m2/one-port/udp-demux.md) - srt-tokio accepts a caller-supplied socket abstraction upstream, or the sans-io fallback is chosen diff --git a/quest/m1/one-port/tcp-demux.md b/quest/m2/one-port/tcp-demux.md similarity index 96% rename from quest/m1/one-port/tcp-demux.md rename to quest/m2/one-port/tcp-demux.md index cdc7148019..4b973b9584 100644 --- a/quest/m1/one-port/tcp-demux.md +++ b/quest/m2/one-port/tcp-demux.md @@ -34,4 +34,4 @@ against one listener each land in the right arm. ## Related -- [UDP demux](/quest/m1/one-port/udp-demux.md) - the UDP half +- [UDP demux](/quest/m2/one-port/udp-demux.md) - the UDP half diff --git a/quest/m1/one-port/udp-demux.md b/quest/m2/one-port/udp-demux.md similarity index 94% rename from quest/m1/one-port/udp-demux.md rename to quest/m2/one-port/udp-demux.md index 017ea18ac8..943a1bf18d 100644 --- a/quest/m1/one-port/udp-demux.md +++ b/quest/m2/one-port/udp-demux.md @@ -12,7 +12,7 @@ recognizable. `moq-sock` gains the demux: a task owning the OS socket (or shard) that classifies each datagram by the rules in the -[questline README](/quest/m1/one-port/README.md) and hands it to the matching +[questline README](/quest/m2/one-port/README.md) and hands it to the matching virtual socket, plus the flow table keyed by 4-tuple that SRT will need, built now so its shape is settled. Each virtual socket implements `AsyncUdpSocket`: `poll_recv` drains its queue, `poll_send` writes through @@ -47,7 +47,3 @@ pins that tuple to WebRTC, so later RTP cannot be classified as SRT. Tests: a unit test per first-byte class routes to the right virtual socket; an integration test runs a QUIC client, a STUN Binding round trip, and a WHIP session against one bound port. `moq-relay` docs list the port once. - -## Related - -- [P2P](/quest/m1/p2p/README.md) - the client side of the STUN answer diff --git a/quest/m1/p2p/README.md b/quest/m2/p2p/README.md similarity index 85% rename from quest/m1/p2p/README.md rename to quest/m2/p2p/README.md index b0817e2379..3ba6d674cf 100644 --- a/quest/m1/p2p/README.md +++ b/quest/m2/p2p/README.md @@ -44,7 +44,7 @@ the best remaining one and the subscription moves back. That is the whole fallback story; nothing TURN-shaped is built. A relay that also answers STUN on its QUIC port -([one port](/quest/m1/one-port/README.md)) is the lowest-RTT STUN server a +([one port](/quest/m2/one-port/README.md)) is the lowest-RTT STUN server a client can name, but this line takes ICE servers from the application and works with any. @@ -77,12 +77,12 @@ negotiated `maxMessageSize` (256 KiB in Chrome). The cost is head-of-line blocking: one lost chunk stalls every stream until SCTP retransmits it. qmux frames already carry stream offsets, so the follow-up is a qmux transport parameter that permits reordering plus receiver -reassembly, which [qmux on the QUIC core](/quest/m1/quic/qmux.md) provides +reassembly, which [qmux on the QUIC core](/quest/m2/quic-qmux.md) provides for free. Both sides advertise that capability in the roster before the channel is created so it can run `ordered: false`; a loss then stalls only the stream it hit. `RTCDataChannel.ordered` cannot change after the channel exists, so the first qmux record is too late to choose. The -[harness](/quest/m1/p2p/harness.md) supplies the numbers that decide when +[harness](/quest/m2/p2p/harness.md) supplies the numbers that decide when that follow-up is worth it. Channel-per-stream is not planned. moq opens a stream per group, so it churns @@ -101,13 +101,13 @@ already declares a random hop id, so browser hops are identified; the roster id is that hop id, held once per origin rather than once per session. Watcher-to-watcher offload needs a tab to forward what it receives, which the -JS origin does not do today: [transit](/quest/m1/p2p/transit.md). +JS origin does not do today: [transit](/quest/m2/p2p/transit.md). How a P2P route outranks the relay's is deliberately open. Costs today live in one scope, a relay mesh pricing its own links; a P2P link is neither free nor the CDN's egress, and `warm` only accumulates, so a tab forwarding the relay's route ties the relay and loses on chain length. -[Cost across scopes](/quest/m1/p2p/cost-scopes.md) writes the rule before +[Cost across scopes](/quest/m2/p2p/cost-scopes.md) writes the rule before the watcher depends on it. No Rust + WASM in-tab hop: [rs2ts](/quest/m1/rs2ts/remove-wasm.md) removes the @@ -127,20 +127,17 @@ WASM build, so the browser side stays TypeScript. ## Required -- [Data channel transport](/quest/m1/p2p/transport.md) - `@moq/p2p` speaks qmux over one ordered RTCDataChannel behind the WebTransport shape `@moq/net` consumes -- [Signaling and policy](/quest/m1/p2p/signal.md) - opted-in peers find each other under the prefix, the application picks who to dial, and the roster-size gate decides whether STUN is used -- [Native data channel transport](/quest/m1/p2p/webrtc.md) - `moq-tokio` holds a moq-net session with a browser over str0m with a full ICE agent -- [moq-cli joins](/quest/m1/p2p/cli.md) - `--p2p` publishes a roster entry with its iroh endpoint, dials iroh between native peers, and serves browsers as a transit hop -- [Transit in the JS origin](/quest/m1/p2p/transit.md) - a tab forwards the routes it receives to its peers with split horizon and its hop id appended -- [Cost across scopes](/quest/m1/p2p/cost-scopes.md) - the written rule for how relay egress, mesh links, and P2P links compare, so a peer that already carries a broadcast wins -- [Watch opts in](/quest/m1/p2p/watch.md) - one attribute turns it on in the demo and the watcher migrates to the cheapest route -- [Harness](/quest/m1/p2p/harness.md) - the Playwright harness and the numbers behind every mapping decision -- [Unordered qmux](/quest/m1/p2p/unordered.md) - qmux tolerates reordering so the data channel runs unordered and a loss stalls one stream +- [Data channel transport](/quest/m2/p2p/transport.md) - `@moq/p2p` speaks qmux over one ordered RTCDataChannel behind the WebTransport shape `@moq/net` consumes +- [Signaling and policy](/quest/m2/p2p/signal.md) - opted-in peers find each other under the prefix, the application picks who to dial, and the roster-size gate decides whether STUN is used +- [Native data channel transport](/quest/m2/p2p/webrtc.md) - `moq-tokio` holds a moq-net session with a browser over str0m with a full ICE agent +- [moq-cli joins](/quest/m2/p2p/cli.md) - `--p2p` publishes a roster entry with its iroh endpoint, dials iroh between native peers, and serves browsers as a transit hop +- [Transit in the JS origin](/quest/m2/p2p/transit.md) - a tab forwards the routes it receives to its peers with split horizon and its hop id appended +- [Cost across scopes](/quest/m2/p2p/cost-scopes.md) - the written rule for how relay egress, mesh links, and P2P links compare, so a peer that already carries a broadcast wins +- [Watch opts in](/quest/m2/p2p/watch.md) - one attribute turns it on in the demo and the watcher migrates to the cheapest route +- [Harness](/quest/m2/p2p/harness.md) - the Playwright harness and the numbers behind every mapping decision +- [Unordered qmux](/quest/m2/p2p/unordered.md) - qmux tolerates reordering so the data channel runs unordered and a loss stalls one stream ## Related - [Peer grants](/quest/m1/auth/peer-grant.md) - the hop-bound credential a direct session presents; HMAC keys issue none -- [One port](/quest/m1/one-port/README.md) - the relay answers STUN on its QUIC port - [E2EE](/quest/m1/e2ee/README.md) - what a peer would need if the token scope stopped being the trust boundary -- [qmux on the QUIC core](/quest/m1/quic/qmux.md) - the stream core the unordered follow-up rides -- [Carrier voice](/quest/m2/carrier-voice/README.md) - signaling as an application protocol over moq, the pattern reused here diff --git a/quest/m1/p2p/cli.md b/quest/m2/p2p/cli.md similarity index 89% rename from quest/m1/p2p/cli.md rename to quest/m2/p2p/cli.md index f0b28f7127..a5e3b16145 100644 --- a/quest/m1/p2p/cli.md +++ b/quest/m2/p2p/cli.md @@ -16,7 +16,7 @@ than adding startup checks. `--p2p-ice-server`, `Peers` takes; `select` is the CLI's built-in default of dialing everyone. The roster entry mirrors the `info.json` schema from -[signaling](/quest/m1/p2p/signal.md): the listener's LAN addresses as +[signaling](/quest/m2/p2p/signal.md): the listener's LAN addresses as WebTransport URLs, ordered the way `mdns::Peer::urls` orders them, with the listener's certificate fingerprint; the iroh endpoint id when `--iroh` is on, which is the first place an endpoint id appears in any roster; `webrtc: true`; @@ -35,5 +35,5 @@ Docs: a "P2P" section in `doc/bin/cli.md` beside the LAN cluster one. ## Required -- [Native data channel transport](/quest/m1/p2p/webrtc.md) -- [Signaling and policy](/quest/m1/p2p/signal.md) - the roster and offer schema this mirrors +- [Native data channel transport](/quest/m2/p2p/webrtc.md) +- [Signaling and policy](/quest/m2/p2p/signal.md) - the roster and offer schema this mirrors diff --git a/quest/m1/p2p/cost-scopes.md b/quest/m2/p2p/cost-scopes.md similarity index 89% rename from quest/m1/p2p/cost-scopes.md rename to quest/m2/p2p/cost-scopes.md index 1aecaa1c19..7fb8c57ff5 100644 --- a/quest/m1/p2p/cost-scopes.md +++ b/quest/m2/p2p/cost-scopes.md @@ -26,7 +26,7 @@ link is neither free (peer uplink, reliability) nor the CDN's egress, and nothing flattens `warm` when an origin already carries the content, so a tab forwarding the relay's route at the relay's price plus its own ties the relay and loses on chain length. Candidate rules to weigh, with the numbers from -the [harness](/quest/m1/p2p/harness.md) where they exist: +the [harness](/quest/m2/p2p/harness.md) where they exist: - a warm discount: an origin actively receiving a broadcast re-announces it at `warm` 0 and `cold` unchanged, over every session, in both languages, @@ -43,10 +43,6 @@ holds when the first hop is a publisher tab, and whether the warm discount belongs in the mesh too. Write the rule beside route selection in `doc/concept/transport.md`, mirror it in `draft-lcurley-moq-lite.md` and `draft-lcurley-moq-cluster.md` where the wire carries it, and pass -`just drafts check`. Then update [watch](/quest/m1/p2p/watch.md) and -[transit](/quest/m1/p2p/transit.md) with the chosen rule and open the +`just drafts check`. Then update [watch](/quest/m2/p2p/watch.md) and +[transit](/quest/m2/p2p/transit.md) with the chosen rule and open the implementation quest it needs. - -## Related - -- [Cluster routing](/quest/m1/cluster-routing.md) - the mesh-side use of cost diff --git a/quest/m1/p2p/harness.md b/quest/m2/p2p/harness.md similarity index 90% rename from quest/m1/p2p/harness.md rename to quest/m2/p2p/harness.md index 9ecd76588a..0863d93dd2 100644 --- a/quest/m1/p2p/harness.md +++ b/quest/m2/p2p/harness.md @@ -36,6 +36,6 @@ points. ## Required -- [moq-cli joins](/quest/m1/p2p/cli.md) -- [Signaling and policy](/quest/m1/p2p/signal.md) -- [Transit in the JS origin](/quest/m1/p2p/transit.md) - the watcher-to-watcher row +- [moq-cli joins](/quest/m2/p2p/cli.md) +- [Signaling and policy](/quest/m2/p2p/signal.md) +- [Transit in the JS origin](/quest/m2/p2p/transit.md) - the watcher-to-watcher row diff --git a/quest/m1/p2p/signal.md b/quest/m2/p2p/signal.md similarity index 91% rename from quest/m1/p2p/signal.md rename to quest/m2/p2p/signal.md index a447ebc850..c088426611 100644 --- a/quest/m1/p2p/signal.md +++ b/quest/m2/p2p/signal.md @@ -11,7 +11,7 @@ size above which new negotiations stop using them. No new relay behavior. ## Plan `Peers` in `@moq/p2p` takes the shared origin and the policy knobs from the -[questline README](/quest/m1/p2p/README.md): `enabled`, `prefix`, +[questline README](/quest/m2/p2p/README.md): `enabled`, `prefix`, `iceServers`, `max`, `meta`, `select`. Publishing into the origin reaches the relay and the origin's announce stream is the roster. Tests inject a peer connection factory so `bun test` covers pairing, the roster gate, and @@ -28,9 +28,9 @@ the presenter public key that [peer grants](/quest/m1/auth/peer-grant.md) bind, the application's `meta`, and for native peers an optional `webtransport: { url, fingerprint }` and `iroh` endpoint id. The schema is shared with -[moq-cli](/quest/m1/p2p/cli.md). Unordered is advertised here so the dialer +[moq-cli](/quest/m2/p2p/cli.md). Unordered is advertised here so the dialer can set `RTCDataChannel.ordered` at create time; see -[unordered qmux](/quest/m1/p2p/unordered.md). +[unordered qmux](/quest/m2/p2p/unordered.md). Pairing is sparse: broadcasts exist only for pairs some `select` chose, so the cost is the dialed pairs, not the square of the roster. Whoever selects a @@ -59,7 +59,7 @@ failure is terminal for the pair until either side re-announces; no retry loop. Each established connection goes through `connect` with the supplied transport on the dialing side and `accept` on the answering side, both with `publish: origin.consume()` and `consume: origin`, so the tab serves what it -publishes and, once [transit](/quest/m1/p2p/transit.md) lands, what it +publishes and, once [transit](/quest/m2/p2p/transit.md) lands, what it receives. Trust: publishing under the prefix proves only that the relay admitted the @@ -81,9 +81,5 @@ there is no equal-scope shortcut. ## Required -- [Data channel transport](/quest/m1/p2p/transport.md) +- [Data channel transport](/quest/m2/p2p/transport.md) - [Peer grants](/quest/m1/auth/peer-grant.md) - the hop-bound, asymmetrically signed credential a direct session presents; HS256 keys issue none - -## Related - -- [Carrier voice protocol](/quest/m2/carrier-voice/README.md) - the same signaling-as-broadcasts shape diff --git a/quest/m1/p2p/transit.md b/quest/m2/p2p/transit.md similarity index 88% rename from quest/m1/p2p/transit.md rename to quest/m2/p2p/transit.md index 20a8f23d9d..32ee77bd3c 100644 --- a/quest/m1/p2p/transit.md +++ b/quest/m2/p2p/transit.md @@ -14,7 +14,7 @@ The JS origin keeps two tables so a received entry can never be announced back to a peer; that separation stays. Transit adds the forwarding step: a route received on session A is announced on every other attached session with the tab's hop id appended to `hops`, the receiving link's cost added -per [cost across scopes](/quest/m1/p2p/cost-scopes.md) once it exists and +per [cost across scopes](/quest/m2/p2p/cost-scopes.md) once it exists and `Cost::UNKNOWN` semantics until then, and dropped when the chain reaches `MAX_HOPS` or already contains the tab's id. A retraction forwards the same way. @@ -25,7 +25,7 @@ from its own origin, which is what the Rust origin already does with splice-and-share behavior for remote routes it serves onward. The hop id must be one per origin, not per session, so the roster id from -[signaling](/quest/m1/p2p/signal.md) and the id in forwarded chains agree. +[signaling](/quest/m2/p2p/signal.md) and the id in forwarded chains agree. Tests with the mock transport pair: a route received on A appears on B with the id appended and never on A; a chain already containing the id is dropped; @@ -34,4 +34,4 @@ subscription. ## Related -- [Watch opts in](/quest/m1/p2p/watch.md) - the first topology that needs a forwarding tab +- [Watch opts in](/quest/m2/p2p/watch.md) - the first topology that needs a forwarding tab diff --git a/quest/m1/p2p/transport.md b/quest/m2/p2p/transport.md similarity index 91% rename from quest/m1/p2p/transport.md rename to quest/m2/p2p/transport.md index 93ab5ab837..4ba0a5931e 100644 --- a/quest/m1/p2p/transport.md +++ b/quest/m2/p2p/transport.md @@ -26,7 +26,7 @@ clamped to the negotiated `maxMessageSize`; a record that would exceed it is split at a frame boundary, never chunked mid-frame. The channel is created with `ordered: true` and no retransmit limit unless signaling already agreed unordered; `ordered` is a constructor option so -[unordered qmux](/quest/m1/p2p/unordered.md) flips it without a second +[unordered qmux](/quest/m2/p2p/unordered.md) flips it without a second transport. The flip is decided from the roster before `createDataChannel`, never from the first qmux record. @@ -41,5 +41,5 @@ the sentence declaring P2P out of scope goes. `just drafts check` passes. ## Related -- [Signaling and policy](/quest/m1/p2p/signal.md) - supplies the peer connection and the agreed ALPN -- [Native data channel transport](/quest/m1/p2p/webrtc.md) - the same binding in Rust +- [Signaling and policy](/quest/m2/p2p/signal.md) - supplies the peer connection and the agreed ALPN +- [Native data channel transport](/quest/m2/p2p/webrtc.md) - the same binding in Rust diff --git a/quest/m1/p2p/unordered.md b/quest/m2/p2p/unordered.md similarity index 87% rename from quest/m1/p2p/unordered.md rename to quest/m2/p2p/unordered.md index 3a8433010c..a4dc1ffbf7 100644 --- a/quest/m1/p2p/unordered.md +++ b/quest/m2/p2p/unordered.md @@ -13,7 +13,7 @@ qmux frames already carry stream offsets; the draft's in-order STREAM requirement exists so a receiver can deliver without reassembly. Add a transport parameter, `unordered`, that both sides must send for the relaxed rule to apply, and receiver-side reassembly by offset for STREAM frames when -it does. Once [qmux on the QUIC core](/quest/m1/quic/qmux.md) lands, that +it does. Once [qmux on the QUIC core](/quest/m2/quic-qmux.md) lands, that reassembly is QUIC's own receive buffer and this quest is the parameter plus the binding flip; before it, do not build a second reassembly buffer. One record is one SCTP message, so the unordered writer emits frames for only @@ -42,12 +42,11 @@ Draft: `draft-lcurley-qmux.md` gains the parameter and the relaxed rule; the data channel row in `draft-lcurley-moq-lite.md` says which mode it runs. `just drafts check` passes. -Measure with the [harness](/quest/m1/p2p/harness.md) before and after: stall +Measure with the [harness](/quest/m2/p2p/harness.md) before and after: stall duration after an induced loss is the number this quest exists to move, and throughput must not regress. ## Required -- [Harness](/quest/m1/p2p/harness.md) - the loss row this quest is measured against -- [qmux on the QUIC core](/quest/m1/quic/qmux.md) - the receive buffer that makes reassembly free -- [Signaling and policy](/quest/m1/p2p/signal.md) - the roster advertisement that decides ordered before the channel exists +- [Harness](/quest/m2/p2p/harness.md) - the loss row this quest is measured against +- [Signaling and policy](/quest/m2/p2p/signal.md) - the roster advertisement that decides ordered before the channel exists diff --git a/quest/m1/p2p/watch.md b/quest/m2/p2p/watch.md similarity index 74% rename from quest/m1/p2p/watch.md rename to quest/m2/p2p/watch.md index 476879b88d..8307f6c064 100644 --- a/quest/m1/p2p/watch.md +++ b/quest/m2/p2p/watch.md @@ -13,17 +13,17 @@ goes away, with no visible interruption. `Peers` on the shared connection's origin with the demo's ICE servers and a `max` from the page; `demo/web` exposes the toggle. The route pick is the origin's, from its cost ranking (`compareRoutes` in `js/net/src/origin.ts`) under the rule from -[cost across scopes](/quest/m1/p2p/cost-scopes.md): a peer already carrying +[cost across scopes](/quest/m2/p2p/cost-scopes.md): a peer already carrying the broadcast wins, and its retraction falls back to the relay. Three topologies and the demo shows all of them: a publishing tab serving watcher tabs directly, watcher tabs pulling from a `moq-cli --p2p` hop, and a watcher tab re-serving to another watcher through -[transit](/quest/m1/p2p/transit.md). +[transit](/quest/m2/p2p/transit.md). ## Required -- [Signaling and policy](/quest/m1/p2p/signal.md) -- [moq-cli joins](/quest/m1/p2p/cli.md) - the native hop the second topology shows -- [Transit in the JS origin](/quest/m1/p2p/transit.md) -- [Cost across scopes](/quest/m1/p2p/cost-scopes.md) +- [Signaling and policy](/quest/m2/p2p/signal.md) +- [moq-cli joins](/quest/m2/p2p/cli.md) - the native hop the second topology shows +- [Transit in the JS origin](/quest/m2/p2p/transit.md) +- [Cost across scopes](/quest/m2/p2p/cost-scopes.md) diff --git a/quest/m1/p2p/webrtc.md b/quest/m2/p2p/webrtc.md similarity index 87% rename from quest/m1/p2p/webrtc.md rename to quest/m2/p2p/webrtc.md index 3b6ee88cb5..4e88a289f1 100644 --- a/quest/m1/p2p/webrtc.md +++ b/quest/m2/p2p/webrtc.md @@ -32,7 +32,7 @@ reliable ordered str0m channel, the way `ws::Upgraded` does, and feed `qmux::Session` through `transport::Session` like `websocket.rs`. One record per message, `max_record_size` 16 KiB by default, clamped to the negotiated message size. `ordered` is a config knob for -[unordered qmux](/quest/m1/p2p/unordered.md), set from the roster before +[unordered qmux](/quest/m2/p2p/unordered.md), set from the roster before the channel is created, never from the first qmux record. Tests: an in-process str0m pair over loopback runs moq-net's session tests; @@ -41,6 +41,5 @@ candidate is offered and selected. Browser interop is the harness's job. ## Related -- [Data channel transport](/quest/m1/p2p/transport.md) - the browser side of the same binding -- [moq-cli joins](/quest/m1/p2p/cli.md) - the first consumer -- [One port](/quest/m1/one-port/README.md) - the relay-side STUN answer this client can be pointed at +- [Data channel transport](/quest/m2/p2p/transport.md) - the browser side of the same binding +- [moq-cli joins](/quest/m2/p2p/cli.md) - the first consumer diff --git a/quest/m2/pipewire-camera-planes.md b/quest/m2/pipewire-camera-planes.md index 5cc58a94f8..c8cda79753 100644 --- a/quest/m2/pipewire-camera-planes.md +++ b/quest/m2/pipewire-camera-planes.md @@ -12,5 +12,4 @@ Unit-test the offer, a multi-block NV12 buffer, and a multi-block I420 buffer, w ## Related -- [Validate PipeWire cameras on a portal and a Pi](/quest/m3/pipewire-camera-hardware.md) - the pass that shows whether a real Pi or portal camera delivers separate planes - [PipeWire DMA-BUFs into Vulkan](/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md) - the hardware validation of the DMA-BUF path diff --git a/quest/m1/processor/README.md b/quest/m2/processor/README.md similarity index 80% rename from quest/m1/processor/README.md rename to quest/m2/processor/README.md index 2adda56916..9d2fefef6a 100644 --- a/quest/m1/processor/README.md +++ b/quest/m2/processor/README.md @@ -24,24 +24,21 @@ service-prefix layout, not `/.pro`. Suffix routing is dropped everywhere, and a prefix claim needs the variable part of the path trailing, so the processor claims its prefix and mirrors the source path beneath it. The source's catalog reaches the output through a -cross-broadcast reference ([media contract](/quest/m1/processor/media-contract.md)). +cross-broadcast reference ([media contract](/quest/m2/processor/media-contract.md)). ## Required -- [Processor media contract](/quest/m1/processor/media-contract.md) - define +- [Processor media contract](/quest/m2/processor/media-contract.md) - define contribution references, source relations, and correlation in the Hang catalog -- [Advertise-only authorization](/quest/m1/processor/advertise-auth.md) - a +- [Advertise-only authorization](/quest/m2/processor/advertise-auth.md) - a worker may advertise its service prefix without receiving permission to publish arbitrary paths under it -- [Expiring media grants](/quest/m1/processor/grant-lease.md) - enforce +- [Expiring media grants](/quest/m2/processor/grant-lease.md) - enforce short-lived exact grants on already-open consumer and producer handles ## Related -- [Reference vision worker](/quest/m3/processor-vision.md) - a runnable worker - publishes frame-correlated detections and proves demand, reconnect, - failover, and teardown end to end - [Wildcard advertisements](/quest/m0/wildcard/README.md) - lets a dormant processor advertise what it could serve without enumerating live sources, and sets the service-prefix layout diff --git a/quest/m1/processor/advertise-auth.md b/quest/m2/processor/advertise-auth.md similarity index 98% rename from quest/m1/processor/advertise-auth.md rename to quest/m2/processor/advertise-auth.md index 08e2985071..3b9d519870 100644 --- a/quest/m1/processor/advertise-auth.md +++ b/quest/m2/processor/advertise-auth.md @@ -17,7 +17,7 @@ announcement or publish request still requires publish permission, so an advertise-only worker cannot bypass the demand exchange. Decided: the advertise scope is prefix-only. Advertising is prefix-only on -every wire (Wildcard's decision) until [announcement shapes](/quest/m3/announce-shapes.md) +every wire (Wildcard's decision) until [announcement shapes](/quest/m2/announce-shapes.md) adds exact and suffix shapes to moq-lite, so leading-star and suffix advertise patterns have nothing to authorize yet; that quest extends this scope. Token claim patterns keep their suffix support for publish and subscribe. diff --git a/quest/m1/processor/grant-lease.md b/quest/m2/processor/grant-lease.md similarity index 100% rename from quest/m1/processor/grant-lease.md rename to quest/m2/processor/grant-lease.md diff --git a/quest/m1/processor/media-contract.md b/quest/m2/processor/media-contract.md similarity index 100% rename from quest/m1/processor/media-contract.md rename to quest/m2/processor/media-contract.md diff --git a/quest/m1/quic/ack-hook.md b/quest/m2/quic-ack-hook.md similarity index 85% rename from quest/m1/quic/ack-hook.md rename to quest/m2/quic-ack-hook.md index 9939c630cc..3f2bdc9b2d 100644 --- a/quest/m1/quic/ack-hook.md +++ b/quest/m2/quic-ack-hook.md @@ -44,15 +44,3 @@ STOP_SENDING by the receiver, and session close. Cut releases of `web-transport-trait` and `web-transport-moq`. The quest completes when both are on crates.io. - -## Required - -- [Per-stream ACK progress in noq](/quest/m1/quic/ack-progress.md) - the - noq-proto accessor the adapter reads - -## Related - -- [Starvation at frame granularity](/quest/m1/qos/starvation-frames.md) - the - moq-net consumer -- [qmux on the QUIC stream state machine](/quest/m1/quic/qmux.md) - decides - what acknowledgment means over a reliable transport diff --git a/quest/m1/quic/ack-progress.md b/quest/m2/quic-ack-progress.md similarity index 94% rename from quest/m1/quic/ack-progress.md rename to quest/m2/quic-ack-progress.md index 747f63d649..b87ca0b86b 100644 --- a/quest/m1/quic/ack-progress.md +++ b/quest/m2/quic-ack-progress.md @@ -36,8 +36,3 @@ size must resolve with the reset instead of hanging. Land it in the fork and offer it upstream once it is stable. The quest completes when a `moq-noq-proto` release carries the accessor and `Cargo.lock` here can name it. - -## Related - -- [poll_acked in web-transport](/quest/m1/quic/ack-hook.md) - the first - consumer of the accessor diff --git a/quest/m2/quic-bbr-google.md b/quest/m2/quic-bbr-google.md deleted file mode 100644 index 63c90992fe..0000000000 --- a/quest/m2/quic-bbr-google.md +++ /dev/null @@ -1,40 +0,0 @@ -# [M] Measure the remaining Google BBR differences - -## Goal - -A measured decision on two remaining differences from Google's public BBR: -recognizing bandwidth growth within a round and precautionary bandwidth -probing. Each gets an adopt, retain, or investigate-further verdict; Google -parity is not assumed to be an improvement and does not gate correctness fixes. - -## Plan - -Use the corrected fork as the baseline. The 2026-09-21 audit compared -noq-proto 1.3.0 / upstream `1a26a8b` with Google Linux BBRv3 `90210de4` -and Google QUICHE `535a2730`. Recheck current upstream sources before testing. -Use QUICHE's actual `bbr3_sender.cc` with its shared `bbr2_*` model; -the older BBR2 sender is not a substitute for its BBR3 state machine. - -- [Google Linux](https://github.com/google/bbr/blob/90210de4b779d40496dee0b89081780eeddf2a60/net/ipv4/tcp_bbr.c#L1924) recognizes growth on any ACK and increments - the plateau counter only at a round boundary. Noq follows - [draft-06](https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-06.html#section-5.3.1.2)'s earlier round-start gate. QUICHE - instead checks its bandwidth maximum at round boundaries. Test bursty and - aggregated ACKs and changing bottleneck capacity for premature plateau exits. -- [Google Linux](https://github.com/google/bbr/blob/90210de4b779d40496dee0b89081780eeddf2a60/net/ipv4/tcp_bbr.c#L1803) and - [Google QUICHE](https://github.com/google/quiche/blob/535a2730e77d47e0dc03746555cc9c34b17bc9e9/quiche/quic/core/congestion_control/bbr3_sender.cc#L1079) stop precautionarily when - probing reaches a previously lossy inflight bound, then accelerate a later - probe if feedback is clean. Noq lacks this state and transition. Compare - shallow queues, capacity increases, random loss, and competing flows. - -Report throughput, queue delay, loss, convergence time, and fairness with -pinned code and configurations. Keep transport differences and QUICHE flags -explicit; compare each algorithm change separately. A simulation result is -not an end-to-end network measurement. Persist a reproducible harness in CI -(at least nightly) and the verdict with the quest's completion. Create a -separate implementation quest for any adopted change rather than silently -expanding this study into a controller rewrite. - -## Related - -- [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - reuse media profiles and measurements -- [Upstream the fork](/quest/m1/quic/upstream.md) - share useful findings with upstream diff --git a/quest/m1/quic/bbr-loss-parity.md b/quest/m2/quic-bbr-loss-parity.md similarity index 100% rename from quest/m1/quic/bbr-loss-parity.md rename to quest/m2/quic-bbr-loss-parity.md diff --git a/quest/m2/quic-bbr-natural-drain.md b/quest/m2/quic-bbr-natural-drain.md index 561b4bea8b..41f5bce0a2 100644 --- a/quest/m2/quic-bbr-natural-drain.md +++ b/quest/m2/quic-bbr-natural-drain.md @@ -43,4 +43,3 @@ separate implementation quest; this study does not silently change defaults. ## Related - [Discover media headroom](/quest/m2/quic-probe.md) - preserving an estimate and discovering spare capacity are separate problems -- [Google BBR comparison](/quest/m2/quic-bbr-google.md) - separate growth and precautionary-probing decisions diff --git a/quest/m2/quic-buffer-pool.md b/quest/m2/quic-buffer-pool.md deleted file mode 100644 index b5e34f03ef..0000000000 --- a/quest/m2/quic-buffer-pool.md +++ /dev/null @@ -1,27 +0,0 @@ -# [S] Send buffer pools - -## Goal - -A measured verdict on pooled send buffers versus `Bytes` in the stream send -path. noq's `SendBuffer` keeps a write above 1452 bytes as the caller's -`Bytes` without copying and coalesces smaller writes into a `BytesMut`; -moq-net hands it frames as `Bytes` from its own allocation. Either a pool of -fixed-size buffers recycled after acknowledgment measurably cuts allocation -and cache misses on the relay's egress, or `Bytes` is shown to be within -noise and the idea is closed. - -## Plan - -- Count allocations and bytes copied per frame on the fanout and video - shapes with the existing profiling captures, split by frame size, so the - share of sub-threshold copies is known before anything is built. -- Prototype in the fork: a `BufFactory`-style seam on `SendBuffer` that takes - buffers from a pool and returns them on ACK, with the pool sized per - connection from the send window. -- `just bench BASE` on Linux: CPU per Gbps, RSS, and p99 latency. Ship only a - measured win. - -## Related - -- [#3204](/quest/m1/perf/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md) - - the UDP-side pool the same buffers could feed diff --git a/quest/m1/quic/deadline.md b/quest/m2/quic-deadline.md similarity index 93% rename from quest/m1/quic/deadline.md rename to quest/m2/quic-deadline.md index 8450cadfa4..09e0efa7a5 100644 --- a/quest/m1/quic/deadline.md +++ b/quest/m2/quic-deadline.md @@ -21,7 +21,7 @@ Implement in the fork. - On loss detection, before queueing a retransmission for a stream with a deadline, estimate the arrival instant as now plus the forward one-way delay. Start with `min_rtt / 2`, corrected by the peer's reported ACK delay; - the [receive-timestamps spike](/quest/m2/quic-receive-ts.md) replaces that + the [receive-timestamps spike](/quest/m3/quic-receive-ts.md) replaces that guess with a measured forward delay. If the estimate is past the deadline, reset the stream with a dedicated error code and drop its retransmit ranges, including bytes already lost, so flow control is returned in one step. @@ -49,7 +49,5 @@ raises loss or latency under any profile stays off by default. ## Related -- [Receive timestamps](/quest/m2/quic-receive-ts.md) - a measured forward - delay replaces the half-RTT estimate - [Discover media headroom](/quest/m2/quic-probe.md) - can reuse retransmission machinery if redundant capacity probes prove worthwhile diff --git a/quest/m1/quic/ecn-measure.md b/quest/m2/quic-ecn-measure.md similarity index 100% rename from quest/m1/quic/ecn-measure.md rename to quest/m2/quic-ecn-measure.md diff --git a/quest/m2/quic-ecn.md b/quest/m2/quic-ecn.md index 3d8e415b83..7040a52394 100644 --- a/quest/m2/quic-ecn.md +++ b/quest/m2/quic-ecn.md @@ -24,7 +24,7 @@ need the fork. default (today's behavior on tokio), and `doc/bin/relay/config.md` documents it in the same PR. - Measure on the netem bottleneck from the - [ECN study](/quest/m1/quic/ecn-measure.md) with `dualpi2` marking against + [ECN study](/quest/m2/quic-ecn-measure.md) with `dualpi2` marking against the same bottleneck dropping: queueing delay, goodput, loss. The study's provider verdict decides whether the result matters outside the lab; if neither Linode nor OVH preserves the marks, L4S stays off and the @@ -32,8 +32,3 @@ need the fork. - ECN visibility stays on the wire: the study observes marks with tcpdump, and exposing per-path ECN state in stats is a later quest if operators need it. - -## Required - -- [Measure ECN on the backbone](/quest/m1/quic/ecn-measure.md) - the - provider verdict this quest acts on diff --git a/quest/m2/quic-send-batching.md b/quest/m2/quic-egress-profile.md similarity index 89% rename from quest/m2/quic-send-batching.md rename to quest/m2/quic-egress-profile.md index e33dea0d72..6ab4bc88f2 100644 --- a/quest/m2/quic-send-batching.md +++ b/quest/m2/quic-egress-profile.md @@ -21,8 +21,3 @@ together, so the question there is whether it already gets the benefit. A measured no-win abandons the tokio change; the io_uring result is recorded either way. - -## Related - -- [Kernel pacing](/quest/m2/quic-kernel-pacing.md) - pacing per train - changes what a batch can contain diff --git a/quest/m2/quic-fec.md b/quest/m2/quic-fec.md deleted file mode 100644 index 5b2f32f098..0000000000 --- a/quest/m2/quic-fec.md +++ /dev/null @@ -1,30 +0,0 @@ -# [L] QUIC FEC experiment - -## Goal - -Record a measured verdict on transport-level forward error correction: whether -spending redundancy before loss beats retransmission on delivery latency, and -at what loss regime it pays. Scope is fork-controlled native peers because -browsers and vanilla WebTransport peers cannot consume custom frames. Ship -nothing by default; abandonment is a valid result. - -## Plan - -Measure the production loss distribution before building. If losses are too -rare or bursty for the candidate codes to recover inside a group's lifetime, -record that result and stop. - -Try the cheapest bounded shapes first: parity across a group's tail packets, -datagram-level parity, then stream-data FEC frames. Keep all framing in the -selected QUIC fork; the MoQ wire does not change. - -Compare against [early retransmission](/quest/m2/quic-probe.md), not only plain -ARQ. Both spend the same spare-bandwidth budget on redundancy. Use the same -netem and real-NIC limits as the -[GCC experiment](/quest/m2/quic-gcc.md), and report delivery latency, goodput, -redundancy cost, and unrecovered group loss. - -## Required - -- [Probe capacity by early retransmission](/quest/m2/quic-probe.md) - supplies - the baseline this experiment must beat diff --git a/quest/m2/quic-kernel-pacing.md b/quest/m2/quic-kernel-pacing.md deleted file mode 100644 index f8d3b32915..0000000000 --- a/quest/m2/quic-kernel-pacing.md +++ /dev/null @@ -1,40 +0,0 @@ -# [M] Kernel pacing - -## Goal - -A measured verdict on handing packet pacing to the kernel. Today noq's pacer -is a userspace token bucket on both runtimes: `poll_transmit` holds a train -until the pacing timer fires, which the io_uring driver arms through -`poll_timeout` like the tokio driver does, but a released GSO train still -leaves the NIC as one burst. The `flush_one` comment in -`rs/moq-uring/src/quic/noq/connection.rs` saying the driver ignores the hint -is stale from quiche. Either the kernel paces each train (`SO_TXTIME` with the `etf` or -`fq` qdisc, per-packet transmit times in the cmsg the driver already builds) -and burstiness at the bottleneck drops without costing CPU, or the burst is -shown not to matter on the fleet's paths. - -## Plan - -- Measure first: on a netem bottleneck, compare inter-packet gaps and queue - occupancy for a 64-segment GSO train against the same bytes paced at the - controller's rate. If the bottleneck absorbs the burst without loss or - delay at the fleet's typical rates, record it and stop. -- Then the io_uring path: stamp each GSO train with `SCM_TXTIME` at the time - noq's pacer would have released it, one timestamp per train (the kernel - spreads segments only with `fq`'s pacing, so test both qdiscs). The socket - is shared across connections per worker, so per-socket rate limits - (`SO_MAX_PACING_RATE`) cannot express per-connection pacing; only - per-packet times can. -- Report CPU per Gbps, loss, and p99 latency against the userspace pacer on - the tokio path and the unpaced io_uring path, on a real NIC as well as - loopback, since `etf` needs hardware offload to be exact. - -The verdict names which qdisc the relay hosts need, or that none is worth -configuring. - -## Related - -- [Send batching](/quest/m2/quic-send-batching.md) - the other syscall-side - lever on the same path -- [#3201](/quest/m1/perf/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - - zero-copy on the same trains diff --git a/quest/m2/quic-probe.md b/quest/m2/quic-probe.md index f1e8ef3e06..85ca7e8c63 100644 --- a/quest/m2/quic-probe.md +++ b/quest/m2/quic-probe.md @@ -47,5 +47,3 @@ retain the baseline and record why before exposing an ineffective option. ## Related - [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - separate ProbeRTT policy experiment -- [FEC experiment](/quest/m2/quic-fec.md) - repetition competes for the redundancy budget -- [GCC egress experiment](/quest/m2/quic-gcc.md) - delay control changes what headroom means diff --git a/quest/m1/quic/qmux.md b/quest/m2/quic-qmux.md similarity index 100% rename from quest/m1/quic/qmux.md rename to quest/m2/quic-qmux.md diff --git a/quest/m2/redundant-ingest.md b/quest/m2/redundant-ingest.md deleted file mode 100644 index 241fe5bb33..0000000000 --- a/quest/m2/redundant-ingest.md +++ /dev/null @@ -1,30 +0,0 @@ -# [M] Redundant ingest under one epoch - -## Goal - -Decide whether and how two live publishers of identical content share one -broadcast, so viewers survive losing one faster than the QUIC keep-alive. -The study may end in a no-go. - -## Plan - -Start from what is documented today (`doc/bin/cli.md` "Redundant -publishers"): two encoders sharing a Hop ID (`--hop 42`) are one first hop, -so relays hold both routes and fail over at a group boundary under the #3312 -same-first-hop rule, provided the tracks are identical with aligned groups. - -Open questions: how that maps onto epochs (the pair claiming one -`@`), what enforces the alignment the docs only ask for (group -sequences, a matching catalog), and who declares the incumbent dead early: a -failover service that retracts it, or active-active delivery to the relay. -[Cluster routing](/quest/m1/cluster-routing.md) drops hop lists inside a -cluster and must decide what replaces this failover; follow its answer. -Weigh them against the moq-transport rule that multiple publishers of a -namespace must each be asked (#3697) and the cluster draft. Output: a -decision, with a quest for the chosen mechanism. - -## Related - -- [Same-hop importers](/quest/m1/hop-aligned-import.md) - identical tracks from one encoded stream under one first hop, the case this generalizes -- [Cluster routing](/quest/m1/cluster-routing.md) - decides what replaces first-hop failover inside a cluster -- [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - explicit epochs are what a redundant pair would share diff --git a/quest/m1/rs2ts/ietf.md b/quest/m2/rs2ts-ietf.md similarity index 84% rename from quest/m1/rs2ts/ietf.md rename to quest/m2/rs2ts-ietf.md index b72b735753..edadfeaa27 100644 --- a/quest/m1/rs2ts/ietf.md +++ b/quest/m2/rs2ts-ietf.md @@ -17,4 +17,3 @@ Public API: breaks `@moq/net`; retargets to `dev`. Wire: none. ## Required - [Generated lite](/quest/m1/rs2ts/lite.md) - the pipeline this reuses -- [Sans-IO IETF session](/quest/m1/rs2ts/sans-io/ietf.md) - the session shape it translates diff --git a/quest/m1/rs2ts/sans-io/ietf.md b/quest/m2/rs2ts-sans-io-ietf.md similarity index 100% rename from quest/m1/rs2ts/sans-io/ietf.md rename to quest/m2/rs2ts-sans-io-ietf.md diff --git a/quest/m2/runtime-qa-hosts.md b/quest/m2/runtime-qa-hosts.md deleted file mode 100644 index 983c22443c..0000000000 --- a/quest/m2/runtime-qa-hosts.md +++ /dev/null @@ -1,43 +0,0 @@ -# [L] On-demand runtime QA hosts - -## Goal - -An agent can submit an exact checkout to an available Linux or hardware test -host and retrieve results, logs, and symbols through a repeatable command. -Unsupported or inaccessible environments remain explicit verification gaps. - -## Plan - -Hosted Windows/macOS recipes compile platform code, while -`quest/m3/video-hardware.md` and `quest/m1/dart-ios.md` already own physical -validation work. This quest supplies the reusable access and execution contract, -not another list of codec or device bugs. - -- Inventory existing authorized hosts and devices first: OS/architecture, - kernel/io_uring capability, GPU/driver, media devices, display/session access, - and debugger/packet-capture permissions. Installed SSH or LLDB is not proof - that a suitable target is reachable. Do not provision paid infrastructure. -- Implement a host-side job runner plus a local submit/status/cancel/artifacts - recipe. Address an immutable source snapshot and record its digest; do not - test whichever branch the remote directory happens to contain. -- Start with one disposable Linux CPU runner for transport/runtime tests. Give - jobs isolated directories, resource limits, leases, and bounded cleanup. - Run untrusted PR code without production credentials or persistent access to - other jobs. Use existing CI or SSH access rather than adding a public service. -- Add one available device profile as proof of extensibility. Keep camera, - microphone, portal, signing, and interactive-session authorization explicit; - report unavailable devices rather than silently using a software fallback. -- Support test-owned stack capture and read-only log/metrics/artifact retrieval. - A separate debug session may retain a failed process with an expiring lease. - Document the human handoff when a permission prompt or device action is needed. - -Acceptance: submit a known SHA, recover its complete evidence, cancel a hung -job, and verify no child process or device lease remains. A mismatched source -digest or absent required backend must fail capability validation. Demonstrate -one real Linux runtime scenario and one available device scenario; if no device -is available, split that rollout into an explicit externally blocked follow-up. - -## Related - -- [Video hardware validation](/quest/m3/video-hardware.md) - owns real encoder/capture/zero-copy verdicts -- [Dart on iOS](/quest/m1/dart-ios.md) - owns simulator/device packaging validation diff --git a/quest/m2/sei/README.md b/quest/m2/sei.md similarity index 60% rename from quest/m2/sei/README.md rename to quest/m2/sei.md index da44b7e4f8..a796c5bf38 100644 --- a/quest/m2/sei/README.md +++ b/quest/m2/sei.md @@ -19,15 +19,6 @@ Total storage and bytes delivered to a video-only subscriber are different measurements. Any future split must state which payloads move and how missing metadata is handled; do not promise byte-faithful export after a deadline miss. -## Required - -- [SEI evidence](/quest/m2/sei/evidence.md) - measure savings and identify a consumer before deciding whether to split -- [SEI section](/quest/m2/sei/sei.md) - define a format only after a positive verdict and settled association policy -- [Rust split and reinsert](/quest/m2/sei/sei-rust.md) - implement the approved split and bounded export behavior -- [Web access](/quest/m2/sei/sei-web.md) - expose approved sidecar samples independently of video - ## Related -- [fMP4 emsg](/quest/m1/emsg.md) - independently settles carriage for metadata already outside video - [Colour model](/quest/m1/color-model.md) - preserves display metadata semantics -- [CEA-608/708](/quest/m1/captions-cea.md) - can read inline caption SEI without waiting for this experiment diff --git a/quest/m2/sei/evidence.md b/quest/m2/sei/evidence.md deleted file mode 100644 index 259915fdac..0000000000 --- a/quest/m2/sei/evidence.md +++ /dev/null @@ -1,32 +0,0 @@ -# [S] Measure whether SEI separation is worthwhile - -## Goal - -Record enough evidence to decide whether a separate SEI track is worth its -framing and reassembly cost. Keep SEI inline while this is unresolved; a no-go -verdict is a valid outcome. - -## Plan - -Measure SEI payload types, bytes, and cadence on representative H.264 and HEVC -inputs. Separate small timing/display metadata, captions, encoder information, -and arbitrary vendor data. Identify a concrete metadata-only consumer if one -motivates the feature. Do not infer typical savings from a synthetic large -payload or from the codec permitting one. - -Compare current inline delivery with the bytes a video-only subscriber could -avoid, including any marker/sidecar overhead. Report total storage separately: -putting the same bytes in two tracks does not itself reduce a complete archive. - -Account for recovery points, display metadata, captions, and unknown payloads; -identify what must remain inline or be restored for each supported receiver. -Compare inline markers, sidecar coverage, and bounded best-effort joining only -if the use case justifies pursuing a split. A deadline bounds waiting but -cannot prove absent metadata never existed. Include loss, late arrival, and -consumer compatibility in the tradeoff. - -Return a recommendation for maintainer agreement. A positive verdict scopes -which payloads to move, whether extraction is opt-in, and the association and -latency contract before the schema or implementation quests start. A negative -verdict abandons the remaining separation quests without affecting unrelated -timed-metadata carriage. diff --git a/quest/m2/sei/sei-rust.md b/quest/m2/sei/sei-rust.md deleted file mode 100644 index b8df34ff0e..0000000000 --- a/quest/m2/sei/sei-rust.md +++ /dev/null @@ -1,37 +0,0 @@ -# [M] Rust SEI split and reinsert - -## Goal - -Rust importers strip H.264/H.265 SEI out of the access unit into the Hang `sei` -sidecar, and Rust exporters put it back byte-faithfully. A round trip through -any supported container reproduces the original bitstream. - -## Plan - -Implement one codec-aware split and reinsert primitive in `moq-mux` and reuse it -from every container gateway rather than creating gateway-local copies. -Apply the split policy selected by the section quest. Preserve inline -payloads that it excludes from splitting, and do not assume arbitrary SEI -can be removed without affecting receiver behavior. - -The exporter joins by group sequence and frame ordinal and restores prefix and -suffix placement and ordering. Placement is exact or it is a loss; there is no -useful approximate reinsertion. - -Export is a live consumer of two independently scheduled tracks, so state its -join budget rather than assuming the sidecar is already there. Reinsert within -the mux buffer the exporter already holds for its audio and video interleave, -and past that budget report the gap using the section's presence signal and -continue. No delivery guarantee is needed, only a stated deadline and an -honest account of what missed it: a silent drop would let export claim a -byte-faithful bitstream it did not produce. - -Round-trip exact NAL bytes through Annex B, length-prefixed samples, MPEG-TS, -and fMP4. Exercise a video-only subscription, a sidecar-only subscription, -a sidecar arriving after its video frame, reconnect at a group boundary, and -bounded cleanup of sidecar samples whose video frame never arrives. - -## Required - -- [SEI section](/quest/m2/sei/sei.md) - defines the catalog and correlation - contract diff --git a/quest/m2/sei/sei-web.md b/quest/m2/sei/sei-web.md deleted file mode 100644 index 0c21839431..0000000000 --- a/quest/m2/sei/sei-web.md +++ /dev/null @@ -1,29 +0,0 @@ -# [S] Web SEI access - -## Goal - -The web Hang stack discovers `sei` sidecars and exposes their samples to -applications, so a browser can read timecode, ad markers, or telemetry without -subscribing to the video track at all. - -## Plan - -Add typed catalog bindings for the section and a subscriber that yields raw SEI -samples keyed by the video group sequence and frame ordinal they came from, the -exact identity the section defines, with the frame timestamp carried as data -for presentation-time sync. Keep it independent -of the decode path: this quest exposes raw sidecar samples. The section -contract must separately establish which payloads can be moved without -requiring restoration before decoding or display. - -Applications parse the NAL payloads themselves with whatever vocabulary they -need, so a new payload type requires no change here. - -Test a sidecar-only subscriber with the video track never requested, a -subscriber that takes both, late join, and reconnect. Prove that a backgrounded -tab consuming only the sidecar draws no video bandwidth. - -## Required - -- [SEI section](/quest/m2/sei/sei.md) - defines the catalog and correlation - contract diff --git a/quest/m2/sei/sei.md b/quest/m2/sei/sei.md deleted file mode 100644 index 5dd18fd98d..0000000000 --- a/quest/m2/sei/sei.md +++ /dev/null @@ -1,54 +0,0 @@ -# [S] SEI catalog section - -## Goal - -Hang defines a top-level `sei` section that relates raw H.264 and H.265 SEI NAL -units to the video access unit they were stripped from. The contract is -sufficient for byte-faithful reinsertion on export and for an application that -subscribes to the sidecar alone. - -## Plan - -The framing below is provisional pending the split-policy and association -decision. Do not treat per-access-unit coverage as settled. - -This is a candidate codec-sidecar contract, not the prerequisite for unrelated -ID3, SCTE-35, emsg, or FLV metadata carriage: a metadata track belongs to exactly one -rendition, uses that rendition's group sequence, stamps each frame with the -wire timestamp of the media it accompanies, and carries raw bytes. A 1080p and -a 360p rendition carry different SEI, so there is one sidecar per video -rendition, and group 7 of the sidecar holds the SEI for group 7 of its video. - -Within a group the frame's wire timestamp is the key, so an application -syncing to presentation time reads it directly instead of joining against the -video track it deliberately did not subscribe to. Several access units can -share a timestamp on the raw Annex B path (`h264::Split::decode` resolves one -clock value per call and gives it to every access unit in that chunk), so the -frame's ordinal within the group disambiguates those; it is a tie-break, not -the identity. - -Preserve prefix or suffix placement, original NAL bytes, and order when -several SEI units accompany one access unit. The codec is the mapped video -rendition's, not serialized again in the sidecar. Placement has to be exact, not -approximate: `recovery_point` on the wrong access unit misdirects a receiver's -tune-in, `pic_timing` breaks field cadence and pulldown, and reordered -CEA-608/708 byte pairs garble a stateful caption decoder. - -Represent whether an access unit had SEI, so a consumer can tell "there was -none" from "lost, pruned, or not yet arrived". Missing SEI is common and valid, -but an exporter cannot claim a byte-faithful reinsertion it did not make, and -without this signal that loss is unreportable. - -Nothing in the video framing changes: the presence signal lives in the sidecar -track's own coverage, not as a flag on video frames, because no consumer blocks -on a sidecar to release a video frame. - -Version the schema so a later semantic view can be added without rewriting the -raw contract. Include fixtures for H.264 and H.265 prefix and suffix SEI, -multiple NAL units on one access unit, frames with no SEI, several access units -sharing one timestamp, and group boundaries. - -## Required - -- [SEI evidence](/quest/m2/sei/evidence.md) - positive evidence is needed before committing the format -- Maintainer approval of a positive split verdict, eligible payloads, and the association and latency policy. diff --git a/quest/m1/signed-priority.md b/quest/m2/signed-priority.md similarity index 100% rename from quest/m1/signed-priority.md rename to quest/m2/signed-priority.md diff --git a/quest/m2/sip-stack.md b/quest/m2/sip-stack.md deleted file mode 100644 index da2df3dabb..0000000000 --- a/quest/m2/sip-stack.md +++ /dev/null @@ -1,34 +0,0 @@ -# [L] SIP media stack - -## Goal - -A `moq-sip` crate that terminates one inbound audio call leg: INVITE-only SIP -over UDP/TCP/TLS, SDP offer/answer for Opus and G.711 (SRTP when offered), and -a plain RTP media leg exposed to the embedder as Opus frames in and out. -Registrar, outbound, video, and DTMF are out of scope; REGISTER gets 405 and -RFC 4733 telephone-events are ignored. This quest completes when the crate is -consumable by an embedder. - -## Plan - -- Open with an evaluation, spike-backed like the - [WebRTC bridge verdict](/quest/m2/livekit-webrtc-bridge.md): drive a real - inbound call through the candidate full Rust SIP stacks (ezk-sip, rvoip, - and whatever else is current) and adopt the one that holds up. Fallback if - none do: an existing parser crate (e.g. rsip) for message/SDP syntax plus a - minimal own INVITE-only transaction and dialog engine - the surface is - small once registrar, proxy, and outbound are out. -- The media leg is plain negotiated RTP: `moq-rtc`/str0m is ICE/DTLS-first - and its reusable session internals are crate-private, so it is precedent, - not a base. Consider publicizing moq-rtc's codec bridges rather than - duplicating the RTP-to-hang mapping, and reuse `moq-audio`'s Opus codec - for the G.711<->Opus transcode, with rate conversion through - `decode::Output` and `encode::Input` since the resampler is private (mono - 8 kHz; G.711 companding is new code, nothing in the repository has it). -- RTP wall-clock normalization off RTCP sender reports, as moq-rtc does. -- The embedder decides paths and auth; the crate's API is - "answer this INVITE, give me the caller as Opus, take Opus to play" plus - call teardown. Silence generation until playback audio exists lives here, - so every embedder gets answer-with-silence for free. -- The SIP edge gateway and line provisioning that consume this crate as an - inbound-call product are moq.pro (downstream) work. diff --git a/quest/m1/qos/starvation-frames.md b/quest/m2/starvation-frames.md similarity index 96% rename from quest/m1/qos/starvation-frames.md rename to quest/m2/starvation-frames.md index 34ab0a1adf..610cce625a 100644 --- a/quest/m1/qos/starvation-frames.md +++ b/quest/m2/starvation-frames.md @@ -52,5 +52,3 @@ unsupported. - [Starvation](/quest/m1/qos/starvation.md) - fixes the wire shape and the group-granularity fallback -- [poll_acked in web-transport](/quest/m1/quic/ack-hook.md) - the released - hook this samples through diff --git a/quest/m2/stats-delta.md b/quest/m2/stats-delta.md index d1243fd974..f53b5aefaf 100644 --- a/quest/m2/stats-delta.md +++ b/quest/m2/stats-delta.md @@ -109,4 +109,3 @@ impact: new on-demand tracks; existing tracks unchanged. ## Related - [Stats format page](/doc/concept/stats.md) - where the new flavor is documented -- [Compressed tracks](/quest/m2/flate/README.md) - group-scoped DEFLATE tracks, whose group-window discipline this flavor repeats diff --git a/quest/m1/stats/encoder-feedback.md b/quest/m2/stats-encoder-feedback.md similarity index 97% rename from quest/m1/stats/encoder-feedback.md rename to quest/m2/stats-encoder-feedback.md index e30ad835ce..ca232eb288 100644 --- a/quest/m1/stats/encoder-feedback.md +++ b/quest/m2/stats-encoder-feedback.md @@ -64,7 +64,5 @@ it already follows. Keyframe requests stay out. ## Related -- [Ladder](/quest/m1/ladder/README.md) - the transcode ladder that adapts to - its uplink today - [Audio follows the grant](/quest/m1/2848-follow-the-bandwidth-grant-in-moq-audio-instead-of.md) - the audio rate follow this signal would feed diff --git a/quest/m2/teleop/README.md b/quest/m2/teleop/README.md index 492354d727..2f125ee661 100644 --- a/quest/m2/teleop/README.md +++ b/quest/m2/teleop/README.md @@ -49,7 +49,7 @@ The split is a framing decision, not a subscription flag, and `moq-json` and modes. What that means for the primitive is in [robot](/quest/m2/teleop/robot.md), and what it means for a protocol multiplexing many message rates onto one link is in -[mavlink](/quest/m2/teleop/mavlink.md). +[mavlink](/quest/m3/teleop-mavlink.md). ### Who is already here @@ -86,31 +86,12 @@ stating plainly because it is what a builder is comparing against. - [Operator arbitration](/quest/m2/teleop/arbitration.md) - exactly one controller commands a vehicle at a time, with explicit handoff and a stated authorization boundary -- [MAVLink bridge](/quest/m2/teleop/mavlink.md) - a `moq-mavlink` gateway - replacing the VPN plus two unmanaged UDP flows, with QGroundControl and - friends unchanged -- [Browser teleoperation package](/quest/m2/teleop/browser-package.md) - `@moq/robot` - mirrors the Rust crate, so browser clients consume the catalog and delivery - classes -- [SITL proof and browser ground station](/quest/m2/teleop/proof.md) - ArduPilot - SITL and a synthetic camera flown from a browser ground station, reproducible - in five minutes -- [V4L2-M2M encoding](/quest/m2/teleop/v4l2-encode.md) - a released `moq-cli` - reaches `moq-video`'s hardware encoder on the boards that fly, and the boards - worth buying are written down - [Teleoperation use-case docs](/quest/m2/teleop/docs.md) - `doc/concept/use-case/` gains a teleoperation page, with a runnable non-media example beside it -- [ROS 2 bridge](/quest/m2/teleop/ros2.md) - a ROS 2 bridge sibling to the - MAVLink one, carrying topics over the same two delivery classes -- [Cross-track correlation](/quest/m2/teleop/correlation.md) - a command, the - telemetry it produced, and the video frame showing the result share one - timebase ## Related - [e2ee](/quest/m1/e2ee/README.md) - the answer for a protected control link -- [Text schema](/quest/m1/text-schema.md) - non-media tracks in a catalog, - arrived at from the media side - [Media stats](/quest/m1/stats/schema.md) - publisher-reported stats on a catalog-announced track (moq#2734); teleop's latency instrumentation extends those types rather than adding a second stats surface diff --git a/quest/m2/teleop/correlation.md b/quest/m2/teleop/correlation.md deleted file mode 100644 index 1da8f218c5..0000000000 --- a/quest/m2/teleop/correlation.md +++ /dev/null @@ -1,32 +0,0 @@ -# [M] Cross-track correlation - -## Goal - -A command, the telemetry sample it produced, and the video frame showing the -result share one timebase, so a teleoperation recording is usable as training -data and an operator can be shown what the machine actually saw. - -## Plan - -The two hosts' clocks are related by deployment assumption, not by the library: -a robot deployment already synchronizes both ends. Each broadcast's fixed -catalog-root `clock: { wall, timescale }` is the bridge. Media and command -tracks keep their own timescales; convert PTS explicitly into the broadcast -clock before joining samples. The robot's video and telemetry share a clock; -the operator's command broadcast supplies its own synchronized mapping. - -Source restarts preserve each broadcast's mapping. There are no per-record -anchors or mutable `set_wall` epochs. Report whether a mapping is present, -but never infer that hosts are synchronized from its presence. State the clock -assumption beside the API, since a join across unsynchronized hosts can look -valid while being wrong. The library provides no clock-sync mechanism; see -[#2278](https://github.com/moq-dev/moq/issues/2278). - -This is also the answer to Kyber's headline claim of continuous drift -computation onto one unified timeline. Worth answering on the merits: -correlating sensor, command and video is the actual product need, and it is -the same property that makes an MCAP recording valuable. - -## Required - -- [Robot teleoperation primitive](/quest/m2/teleop/robot.md) diff --git a/quest/m2/teleop/ros2.md b/quest/m2/teleop/ros2.md deleted file mode 100644 index a7d6dfc919..0000000000 --- a/quest/m2/teleop/ros2.md +++ /dev/null @@ -1,38 +0,0 @@ -# [L] ROS 2 bridge - -## Goal - -A ROS 2 bridge sibling to the MAVLink one, carrying topics over the same two -delivery classes. - -## Plan - -### It must land in MCAP - -Recordability is the axis this loses on if ignored. `rtsp_image_transport` -publishes a URL string, so rosbag2 records the URL and not the video; Foxglove's -`CompressedVideo` beat it despite banning B-frames and re-stapling parameter -sets to every keyframe, purely because it stays a recordable message. -Teleoperation video is training data, so a bridge that breaks the recording -loses regardless of latency. - -### Why not DDS - -Nobody runs RTPS over a WAN: discovery data grows quadratically, there is no -NAT traversal short of a port-forward rule per pair of communicating clients, -and reliable QoS on a lossy link produces latency spikes rather than delivery. -Every serious deployment already terminates DDS at the edge and -re-encapsulates. The competitors are the ones named in the questline README, -not the middleware. - -### Sizing - -Larger than the MAVLink bridge: type handling, QoS mapping, and the MCAP -requirement are each real work. Take it after the primitive has one integration -proving it, which is why it requires the MAVLink bridge rather than only the -crate. - -## Required - -- [Robot teleoperation primitive](/quest/m2/teleop/robot.md) -- [MAVLink bridge](/quest/m2/teleop/mavlink.md) diff --git a/quest/m2/teleop/v4l2-encode.md b/quest/m2/teleop/v4l2-encode.md deleted file mode 100644 index 222844d62b..0000000000 --- a/quest/m2/teleop/v4l2-encode.md +++ /dev/null @@ -1,43 +0,0 @@ -# [S] V4L2-M2M encoding - -## Goal - -A released `moq-cli` encodes in hardware on the boards that fly, so a Raspberry -Pi needs no GStreamer detour, and the hardware worth buying is written down. - -## Plan - -The backend exists: `moq-video`'s opt-in `v4l2` feature drives the stateful -V4L2 M2M encoder and decoder through `rs/moq-video/src/v4l2.rs`, and both have -run on a Pi 4 (`bcm2835-codec`). What is left is getting it into people's -hands. - -Released `moq-cli` binaries are built with default features, so neither -`capture` nor `v4l2` is compiled in and the backend reaches nobody who installs -a release. A hardware encoder nobody can install is not a fix. [CLI -packaging](/quest/m1/cli-packaging.md) makes capture available; this quest must -also enable `v4l2` in the Linux ARM release build. It finishes once a released -binary on a Pi 4 publishes from `moq import capture` through the hardware -encoder. - -Write the hardware note down, in `doc/bin/cli.md` next to the capture build -instructions. Two landmines make it part of the deliverable: Raspberry Pi 5 has -no video encoder at all, and Jetson Orin Nano ships without NVENC. The boards -that still encode are Pi 4/CM4/Zero 2 W, Orin NX and above, and RK3588. -Rockchip stays excluded from the backend: RK3588 encoding goes through rkmpp in -a vendor kernel rather than V4L2, and the existing `moq-gst` route already ships -aarch64 packages for it. Adding an rkmpp backend is a separate decision. - -Validate what the Pi 4 run did not reach: `set_bitrate` on a running encoder -(congestion control retunes through it), resolutions past 640x360 where 1080p -codes as 1088 rows and the compose rectangle crops it back, and the other -`bcm2835-codec` boards (Zero 2 W, CM4). - -## Required - -- [CLI packaging](/quest/m1/cli-packaging.md) - the released binary has to - compile `capture` before a hardware encoder in it reaches anyone - -## Related - -- [Video hardware validation](/quest/m3/video-hardware.md) - the other unproven encoder backend, VAAPI diff --git a/quest/m1/text-schema.md b/quest/m2/text-schema.md similarity index 100% rename from quest/m1/text-schema.md rename to quest/m2/text-schema.md diff --git a/quest/m2/ts-import-health.md b/quest/m2/ts-import-health.md index faf45a6a4d..77188dc12b 100644 --- a/quest/m2/ts-import-health.md +++ b/quest/m2/ts-import-health.md @@ -81,7 +81,7 @@ Decided while planning [#1838](https://github.com/moq-dev/moq/issues/1838): - **No opaque whole-mux lane**: [#1861](https://github.com/moq-dev/moq/issues/1861) is closed not planned and verbatim TS is a non-goal in - [MSFTS convergence](/quest/m4/msfts-convergence.md). P3 is out of this set; + [MSFTS convergence](/quest/m2/msfts-convergence.md). P3 is out of this set; `CAT_error` too, since the lane carries no scrambled service. A scrambled PAT or PMT still counts under its own check. diff --git a/quest/m1/perf/uring-open-contract.md b/quest/m2/uring-open-contract.md similarity index 85% rename from quest/m1/perf/uring-open-contract.md rename to quest/m2/uring-open-contract.md index f49a4e3196..0acd060717 100644 --- a/quest/m1/perf/uring-open-contract.md +++ b/quest/m2/uring-open-contract.md @@ -18,7 +18,3 @@ Update the implementation quest with the selected state transitions, resource bounds, cancellation behavior, and regression cases for concurrent openers, credit starvation, dropped futures, and finish/drop. This quest ships the plan; it does not close #3129 or implement an unsettled public contract. - -## Related - -- [Write headers at open](/quest/m1/perf/3129-moq-uring-write-the-webtransport-stream-header-at-open.md) - implementation after the contract is settled diff --git a/quest/m1/uring-tcp/README.md b/quest/m2/uring-tcp/README.md similarity index 90% rename from quest/m1/uring-tcp/README.md rename to quest/m2/uring-tcp/README.md index f18fc50537..8be87155ee 100644 --- a/quest/m1/uring-tcp/README.md +++ b/quest/m2/uring-tcp/README.md @@ -35,9 +35,9 @@ number is what justifies the rest of the line. ## Required -- [Ablation](/quest/m1/uring-tcp/ablation.md) - measure ring TCP against tokio +- [Ablation](/quest/m2/uring-tcp/ablation.md) - measure ring TCP against tokio TCP under the qmux workload before committing to the port -- [Stream](/quest/m1/uring-tcp/stream.md) - a `tcp` module in `moq-uring`, and +- [Stream](/quest/m2/uring-tcp/stream.md) - a `tcp` module in `moq-uring`, and the `hyper::rt` adapters that let axum run on it -- [Relay](/quest/m1/uring-tcp/relay.md) - serve the relay's WebSocket and +- [Relay](/quest/m2/uring-tcp/relay.md) - serve the relay's WebSocket and stream listeners from the io_uring workers diff --git a/quest/m1/uring-tcp/ablation.md b/quest/m2/uring-tcp/ablation.md similarity index 100% rename from quest/m1/uring-tcp/ablation.md rename to quest/m2/uring-tcp/ablation.md diff --git a/quest/m1/uring-tcp/relay.md b/quest/m2/uring-tcp/relay.md similarity index 92% rename from quest/m1/uring-tcp/relay.md rename to quest/m2/uring-tcp/relay.md index c942711662..cfce78c520 100644 --- a/quest/m1/uring-tcp/relay.md +++ b/quest/m2/uring-tcp/relay.md @@ -13,7 +13,7 @@ the operator at a separate `init_streams` tokio server (rs/moq-relay/src/uring.rs:116-120); the only thing it refuses is `tls.generate` (:126-128). Replace the silent skip with real support: each worker binds its own listener in the reuseport group and runs the router from -[stream](/quest/m1/uring-tcp/stream.md) on it. +[stream](/quest/m2/uring-tcp/stream.md) on it. The split of work stays what `uring.rs` already documents (:10-15): the worker owns everything transport-shaped, while authentication and session @@ -29,5 +29,5 @@ worker. ## Required -- [Stream](/quest/m1/uring-tcp/stream.md) - the module and adapters this +- [Stream](/quest/m2/uring-tcp/stream.md) - the module and adapters this serves from diff --git a/quest/m1/uring-tcp/stream.md b/quest/m2/uring-tcp/stream.md similarity index 96% rename from quest/m1/uring-tcp/stream.md rename to quest/m2/uring-tcp/stream.md index 780f6d9c32..c0ce1630d2 100644 --- a/quest/m1/uring-tcp/stream.md +++ b/quest/m2/uring-tcp/stream.md @@ -32,4 +32,4 @@ existing suite does. ## Required -- [Ring TCP ablation](/quest/m1/uring-tcp/ablation.md) - a positive verdict selects the mechanisms before the worker implementation starts +- [Ring TCP ablation](/quest/m2/uring-tcp/ablation.md) - a positive verdict selects the mechanisms before the worker implementation starts diff --git a/quest/m2/video-codec-coverage.md b/quest/m2/video-codec-coverage.md index 36b369303b..7b207a06f6 100644 --- a/quest/m2/video-codec-coverage.md +++ b/quest/m2/video-codec-coverage.md @@ -30,5 +30,4 @@ Public API and wire: no changes during this study. ## Related - [NVIDIA formats](/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md) - existing AV1 encode and 10-bit scope -- [VAAPI](/quest/m4/video-vaapi.md) - existing Linux codec expansion - [VP8/VP9](/quest/m1/obs-moq-video/vpx.md) - existing portable decoder scope diff --git a/quest/m4/video-vaapi.md b/quest/m2/video-vaapi.md similarity index 94% rename from quest/m4/video-vaapi.md rename to quest/m2/video-vaapi.md index 1f3ee5658a..e2bc947fce 100644 --- a/quest/m4/video-vaapi.md +++ b/quest/m2/video-vaapi.md @@ -48,7 +48,3 @@ already falls back cleanly, since `Encoder::new` returns `Err` and - A `moq-dev/vaapi` release exposing an HEVC encoder (H.264 decode is in 0.0.4; DMA-BUF encode and VPP shipped in 0.1.0) and pre-generated bindings instead of a bindgen build script - -## Related - -- [Pool VAAPI resize surfaces](/quest/m4/vaapi-resize-pool.md) - reuse one VPP output surface per size, a separate moq-vaapi release diff --git a/quest/m2/x11-capture-shm.md b/quest/m2/x11-capture-shm.md index 15e21051fe..46e53d3ca5 100644 --- a/quest/m2/x11-capture-shm.md +++ b/quest/m2/x11-capture-shm.md @@ -26,7 +26,3 @@ there to be consumed and the round trip dropped. Both changes are contained to the one backend and are verifiable on a Linux host with a real X session; CI compiles the file but cannot run it. - -## Related - -- [Capture frame buffers](/quest/m2/capture-frame-buffers.md) - the per-frame allocations in the same read path diff --git a/quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md b/quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md deleted file mode 100644 index a911c2f655..0000000000 --- a/quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md +++ /dev/null @@ -1,66 +0,0 @@ -# [M] video: validate PipeWire DMA-BUF capture on KDE hardware - -## Goal - -Implement and verify the behavior tracked in [#2893](https://github.com/moq-dev/moq/issues/2893) -within the issue's stated scope and boundaries. - -## Plan - -Use the public issue's scope, implementation notes, and acceptance criteria -below as the starting plan. Reconcile paths and assumptions with the current -tree before implementation. - -### Issue context - -#### Problem - -Hardware validation for #2839 did not complete on a KDE/Wayland desktop. The screen source was selected in the portal, but the ignored PipeWire capture test never received a frame and timed out after 120 seconds. - -This is a focused follow-up to the broader Linux zero-copy tracker in #2819. - -#### Environment - -- KDE desktop on Wayland -- `XDG_CURRENT_DESKTOP=KDE` -- `WAYLAND_DISPLAY=wayland-0` -- `DISPLAY=:0` -- `XDG_RUNTIME_DIR=/run/user/1000` -- Active desktop D-Bus session - -#### Reproduction - -Run from the Nix development shell after #2839: - -```sh -cargo nextest run --profile ci -p moq-video --all-features --run-ignored ignored-only portal_captures_frames --no-capture -``` - -During review, a temporary assertion also required captured frames to be `Surface::DmaBuf`. The run reached the test, the portal source was selected, then nextest reported the test as slow after 60 seconds and terminated it at 120 seconds without receiving a frame. - -#### Expected - -- Portal selection completes. -- PipeWire format and buffer negotiation completes. -- The first frame arrives within the existing capture timeout. -- A DMA-BUF-capable compositor produces `Surface::DmaBuf`; shared memory remains a working fallback. - -#### Validation - -- \[ ] Reproduce the post-selection timeout on KDE/Wayland. -- \[ ] Add tracing around portal completion, PipeWire connection, format fixation, buffer allocation, and first-frame delivery to locate the stall. -- \[ ] Validate packed RGB DMA-BUF capture and Vulkan import on Intel or AMD hardware. -- \[ ] Validate the linear DMA-BUF CPU fallback. -- \[ ] Confirm shared-memory PipeWire capture still works when DMA-BUF is unavailable. -- \[ ] Hold multiple frames long enough to exercise buffer leasing without pool exhaustion or reused content. -- \[ ] Add or refine an ignored hardware test so the observed failure is distinguishable from a portal-selection timeout. - -Refs #2839 and #2819. - -## Closes - -- [#2893](https://github.com/moq-dev/moq/issues/2893) - close this issue when the quest finishes - -## Related - -- [#2819: moq-video: carry PipeWire DMA-BUFs safely into the Vulkan renderer](/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md) - related open work diff --git a/quest/m1/perf/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md b/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md similarity index 100% rename from quest/m1/perf/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md rename to quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md diff --git a/quest/m1/perf/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md b/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md similarity index 92% rename from quest/m1/perf/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md rename to quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md index 86f4ebb14a..829281806d 100644 --- a/quest/m1/perf/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md +++ b/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md @@ -34,10 +34,6 @@ The TX pool already owns stable `Box<[u8]>` allocations and grows lazily. If zer Compare #3201 with and without registered buffers using the same threshold and workload matrix. Record CPU, cycles, registration cost, locked memory, pool starvation, throughput, and latency. Do not add the complexity unless it improves the winning zero-copy range beyond ordinary `SendMsgZc`. -## Required - -- [#3201: moq-uring: use SENDMSG_ZC for large UDP GSO trains](/quest/m1/perf/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - complete the prerequisite issue first - ## Closes - [#3204](https://github.com/moq-dev/moq/issues/3204) - close this issue when the quest finishes diff --git a/quest/m3/README.md b/quest/m3/README.md index 5f1ec16b65..1c130ea9b7 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -14,11 +14,34 @@ condition clears, move the quest to the milestone its work belongs in. ## Required -- [DPDK](/quest/m3/dpdk.md) - a kernel-bypass UDP path for the relay, once a provider offers SR-IOV or bare metal - [Video hardware validation](/quest/m3/video-hardware.md) - run the encode, capture, and zero-copy paths that were written but never run on real machines -- [Validate PipeWire cameras on a portal and a Pi](/quest/m3/pipewire-camera-hardware.md) - run the shipped PipeWire camera through the camera portal and a Pi CSI node -- [#2893](/quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md) - video: validate PipeWire DMA-BUF capture on KDE hardware - [Embedded video](/quest/m3/video-embedded.md) - EGL import in the renderer, so moq-video presents on a Pi -- [Vision worker](/quest/m3/processor-vision.md) - a documented customer-run vision worker proves the processor contract -- [Announcement shapes](/quest/m3/announce-shapes.md) - moq-lite announcements and interests carry prefix, exact, suffix, or prefix+suffix shapes that survive relay hops, benchmarked over the announce table -- [Upstream forks](/quest/m3/upstream-forks.md) - offer the uniffi generator fixes our cpp, dart, and Python forks carry upstream, lowest priority +- [#3201: moq-uring: use SENDMSG_ZC for large UDP GSO trains](/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - complete the prerequisite issue first +- [#3204](/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md) - moq-uring: register TX-pool buffers for zero-copy sends +- [Receive timestamps](/quest/m3/quic-receive-ts.md) - per-packet arrival times in ACKs, the feedback GCC and deadlines need +- [QUIC GCC](/quest/m3/quic-gcc.md) - a measured verdict on delay-based congestion control for media egress, shipping as `RealTime` +- [AF_XDP UDP path](/quest/m3/af-xdp.md) - the kernel-bypass verdict on today's virtio hosts that gates DPDK +- [C# through moq-ffi](/quest/m3/cs/README.md) - generated C# over moq-ffi as a NuGet package with native runtimes +- [Unity prototype](/quest/m3/unity.md) - the C# package under IL2CPP, playing subscribed audio +- [Unreal prototype](/quest/m3/unreal.md) - a UE5 module on the C++ package with exceptions disabled, rendering a subscribed broadcast to a texture +- [LiveKit client shim](/quest/m3/livekit-shim.md) - a media compatibility facade over the room SDK +- [Conan remote](/quest/m3/cpp-conan.md) - a remote we own serves the same tarball to `conan install` +- [WHEP ABR](/quest/m3/whep-abr.md) - a WHEP viewer switches renditions from its own congestion feedback +- [Synced data playback](/quest/m3/watch-data-sync.md) - js/watch releases JSON and binary payloads on the media playhead, and a slow data track holds media back +- [Linux OBS GPU input](/quest/m3/obs-linux-gpu.md) - publish OBS compositor frames without CPU readback on a validated Linux graphics/encoder combination +- [Routing cost domains](/quest/m3/routing-cost-domains.md) - design operator boundaries and policy without adding incomparable costs +- [Common Access Tokens](/quest/m3/cat/README.md) - a moq-transport client presents a CAT in SETUP and `moq auth serve` admits it with the scope its `moqt` claim names +- [Drop the hidden cluster exemption](/quest/m3/hidden-exemption.md) - relays stop forcing hidden broadcasts on cluster peers once every peer opts in on the wire +- [MAVLink bridge](/quest/m3/teleop-mavlink.md) - a `moq-mavlink` gateway + replacing the VPN plus two unmanaged UDP flows, with QGroundControl and + friends unchanged +- [SITL proof and browser ground station](/quest/m3/teleop-proof.md) - ArduPilot + SITL and a synthetic camera flown from a browser ground station, reproducible + in five minutes +- [Browser teleoperation package](/quest/m3/teleop-browser-package.md) - `@moq/robot` + mirrors the Rust crate, so browser clients consume the catalog and delivery + classes +- [Encode config](/quest/m3/intra-refresh-encode-config.md) - refresh mode extends the settled GOP contract; the producer cuts groups per sweep and publishes `warmup` +- [NVENC refresh](/quest/m3/intra-refresh-nvenc.md) - the NVENC backend encodes refresh mode for H.264 and HEVC +- [V4L2 refresh](/quest/m3/intra-refresh-v4l2.md) - the V4L2 backend encodes refresh mode +- [Bindings](/quest/m3/intra-refresh-bindings.md) - moq-ffi and every wrapper expose refresh mode, additive on the ffi-shape `Gop` enum diff --git a/quest/m2/af-xdp.md b/quest/m3/af-xdp.md similarity index 82% rename from quest/m2/af-xdp.md rename to quest/m3/af-xdp.md index 775b7b34c6..7a521fa67d 100644 --- a/quest/m2/af-xdp.md +++ b/quest/m3/af-xdp.md @@ -18,9 +18,3 @@ closed until hardware changes. - Measure packets per second, CPU per Gbps, and p99 latency on one virtio host in copy mode, on the chat and fanout shapes, against the io_uring path with the zero-copy and busy-poll quests' best settings. - -## Related - -- [DPDK](/quest/m3/dpdk.md) - the full bypass this verdict gates -- [#3203](/quest/m1/perf/3203-moq-uring-add-opt-in-napi-busy-polling.md) - - the in-kernel ceiling this competes with diff --git a/quest/m2/cat/README.md b/quest/m3/cat/README.md similarity index 96% rename from quest/m2/cat/README.md rename to quest/m3/cat/README.md index d68c4ec7d5..6068a26f90 100644 --- a/quest/m2/cat/README.md +++ b/quest/m3/cat/README.md @@ -49,10 +49,10 @@ one. Everything rides `moq_auth::Request` and ## Required -- [Verify](/quest/m2/cat/verify.md) - `moq_auth::cat` turns a CAT into a +- [Verify](/quest/m3/cat/verify.md) - `moq_auth::cat` turns a CAT into a grant and `moq auth serve` admits one; `moq auth sign|verify` mint and check the format -- [Present](/quest/m2/cat/present.md) - a CAT is one kind of configured +- [Present](/quest/m3/cat/present.md) - a CAT is one kind of configured token, riding the SETUP option the in-band token quest already writes ## Related diff --git a/quest/m2/cat/present.md b/quest/m3/cat/present.md similarity index 97% rename from quest/m2/cat/present.md rename to quest/m3/cat/present.md index 8fcd518586..fcdc6f9551 100644 --- a/quest/m2/cat/present.md +++ b/quest/m3/cat/present.md @@ -40,7 +40,7 @@ Public API: additive on `moq-tokio` and `js/net`. Wire: none. ## Required -- [Verify](/quest/m2/cat/verify.md) - the server that admits the token the +- [Verify](/quest/m3/cat/verify.md) - the server that admits the token the end-to-end test presents - [Token in band](/quest/m1/auth/token-in-band.md) - the token configuration and setup-option writer this adds a kind to diff --git a/quest/m2/cat/verify.md b/quest/m3/cat/verify.md similarity index 100% rename from quest/m2/cat/verify.md rename to quest/m3/cat/verify.md diff --git a/quest/m2/cpp-conan.md b/quest/m3/cpp-conan.md similarity index 100% rename from quest/m2/cpp-conan.md rename to quest/m3/cpp-conan.md diff --git a/quest/m2/cs/README.md b/quest/m3/cs/README.md similarity index 83% rename from quest/m2/cs/README.md rename to quest/m3/cs/README.md index 69c17b0a8d..2fcf1726c2 100644 --- a/quest/m2/cs/README.md +++ b/quest/m3/cs/README.md @@ -19,10 +19,9 @@ up front. ## Required -- [Generator](/quest/m2/cs/generator.md) - uniffi-bindgen-cs on uniffi 0.32, pinned and generating `cs/ffi` in CI -- [Package](/quest/m2/cs/package.md) - the `cs/moq` wrapper, NuGet package with native runtimes, interop client, and docs +- [Generator](/quest/m3/cs/generator.md) - uniffi-bindgen-cs on uniffi 0.32, pinned and generating `cs/ffi` in CI +- [Package](/quest/m3/cs/package.md) - the `cs/moq` wrapper, NuGet package with native runtimes, interop client, and docs ## Related - [C++ through moq-ffi](/quest/m1/cpp/README.md) - the sibling line this copies -- [Unity prototype](/quest/m2/unity.md) - the package under IL2CPP diff --git a/quest/m2/cs/generator.md b/quest/m3/cs/generator.md similarity index 100% rename from quest/m2/cs/generator.md rename to quest/m3/cs/generator.md diff --git a/quest/m2/cs/package.md b/quest/m3/cs/package.md similarity index 94% rename from quest/m2/cs/package.md rename to quest/m3/cs/package.md index 5b5eadd73c..7937460b0f 100644 --- a/quest/m2/cs/package.md +++ b/quest/m3/cs/package.md @@ -24,4 +24,4 @@ interop --all` and `doc/lib/cs` documents the package. ## Required -- [Generator](/quest/m2/cs/generator.md) - the pinned generator that emits `cs/ffi` +- [Generator](/quest/m3/cs/generator.md) - the pinned generator that emits `cs/ffi` diff --git a/quest/m3/dpdk.md b/quest/m3/dpdk.md deleted file mode 100644 index efaf48e21c..0000000000 --- a/quest/m3/dpdk.md +++ /dev/null @@ -1,28 +0,0 @@ -# [XL] DPDK - -## Goal - -A relay build whose io_uring workers are replaced by DPDK poll-mode workers -owning a NIC queue each, measured against the kernel path on the same host. -Worth building only on hardware with SR-IOV virtual functions or a dedicated -NIC; neither Linode nor OVH VPS, where the fleet runs, offers either. - -## Plan - -Do not start until the [AF_XDP spike](/quest/m2/af-xdp.md) has a verdict: it -runs on today's hosts and bounds what bypass can buy. If it shows the kernel -path is within reach of line rate, this quest is abandoned. - -When a provider offers the hardware: a `moq-dpdk` worker crate mirroring -`moq-uring`'s worker contract (one QUIC endpoint per worker, connection-ID -steering by the NIC's RSS or a flow rule), userspace UDP/IP, and the noq -endpoint driven from the poll loop. Measure packets per second, CPU per -Gbps, and p99 latency against io_uring on the same box, then decide whether -a bare-metal tier is worth operating. - -## Required - -- [AF_XDP UDP path](/quest/m2/af-xdp.md) - the no-hardware verdict this waits - on; a kernel path within reach of line rate abandons this quest -- A moq.pro relay provider offers SR-IOV or bare-metal hosts the fleet can - run on diff --git a/quest/m2/hidden-exemption.md b/quest/m3/hidden-exemption.md similarity index 100% rename from quest/m2/hidden-exemption.md rename to quest/m3/hidden-exemption.md diff --git a/quest/m2/intra-refresh/bindings.md b/quest/m3/intra-refresh-bindings.md similarity index 90% rename from quest/m2/intra-refresh/bindings.md rename to quest/m3/intra-refresh-bindings.md index 78bbc95863..b6cb07cf5c 100644 --- a/quest/m2/intra-refresh/bindings.md +++ b/quest/m3/intra-refresh-bindings.md @@ -21,5 +21,4 @@ both modes. ## Required -- [Encode config](/quest/m2/intra-refresh/encode-config.md) - the core refresh variant this mirrors - [Codecs](/quest/m1/ffi-shape/codec.md) - the `MoqVideoGop` enum this extends diff --git a/quest/m2/intra-refresh/encode-config.md b/quest/m3/intra-refresh-encode-config.md similarity index 100% rename from quest/m2/intra-refresh/encode-config.md rename to quest/m3/intra-refresh-encode-config.md diff --git a/quest/m2/intra-refresh/nvenc-refresh.md b/quest/m3/intra-refresh-nvenc.md similarity index 91% rename from quest/m2/intra-refresh/nvenc-refresh.md rename to quest/m3/intra-refresh-nvenc.md index 5040bc67a0..f07c0d3492 100644 --- a/quest/m2/intra-refresh/nvenc-refresh.md +++ b/quest/m3/intra-refresh-nvenc.md @@ -26,7 +26,3 @@ refuses the mode. splitters to confirm one group per sweep and the SEI count, and record the numbers in the PR. Any test that needs the GPU skips loudly rather than reporting success. - -## Required - -- [Encode config](/quest/m2/intra-refresh/encode-config.md) - the `Gop` enum and cut semantics this implements diff --git a/quest/m2/intra-refresh/v4l2-refresh.md b/quest/m3/intra-refresh-v4l2.md similarity index 93% rename from quest/m2/intra-refresh/v4l2-refresh.md rename to quest/m3/intra-refresh-v4l2.md index 20b3c1fb1d..6d2f5d9aa9 100644 --- a/quest/m2/intra-refresh/v4l2-refresh.md +++ b/quest/m3/intra-refresh-v4l2.md @@ -31,7 +31,3 @@ claiming a boundary that was not encoded. - Verify on the hardware the backend already targets that the first sweep begins at frame zero and note whether the driver emits the SEI; the hang side does not need it. - -## Required - -- [Encode config](/quest/m2/intra-refresh/encode-config.md) - the `Gop` enum and cut semantics this implements diff --git a/quest/m2/livekit-shim.md b/quest/m3/livekit-shim.md similarity index 95% rename from quest/m2/livekit-shim.md rename to quest/m3/livekit-shim.md index 6e5697e38c..ba66c60475 100644 --- a/quest/m2/livekit-shim.md +++ b/quest/m3/livekit-shim.md @@ -30,7 +30,3 @@ and the connect URL/token changed. - Recommend tokens scoped to `put: /` so participants cannot publish at each other's paths (hang.live grants `put` on the whole room subtree today). - -## Related - -- [WebRTC bridge evaluation](/quest/m2/livekit-webrtc-bridge.md) diff --git a/quest/m2/obs-linux-gpu.md b/quest/m3/obs-linux-gpu.md similarity index 92% rename from quest/m2/obs-linux-gpu.md rename to quest/m3/obs-linux-gpu.md index 7d29c34ec7..9b364d389a 100644 --- a/quest/m2/obs-linux-gpu.md +++ b/quest/m3/obs-linux-gpu.md @@ -19,5 +19,4 @@ A supported Linux OBS graphics/encoder combination publishes composited video wi ## Related -- [VAAPI encode and decode](/quest/m4/video-vaapi.md) - H.265 and checked-in bindings. The DMA-BUF encoder import this quest needs is already on main (moq-vaapi 0.1.0). - [Video hardware validation](/quest/m3/video-hardware.md) - native input and encoder acceptance need hardware evidence diff --git a/quest/m3/pipewire-camera-hardware.md b/quest/m3/pipewire-camera-hardware.md deleted file mode 100644 index 29ac05026d..0000000000 --- a/quest/m3/pipewire-camera-hardware.md +++ /dev/null @@ -1,23 +0,0 @@ -# [S] Validate PipeWire cameras on a portal and a Pi - -## Goal - -The xdg-desktop-portal Camera path and a Raspberry Pi CSI camera each capture frames through the PipeWire camera that already shipped, or this quest records what stopped the pass. There is no new capture API. - -## Plan - -Open `pipewire` and one `pipewire:` in a sandbox, where the portal raises its permission dialog, and on a Pi whose CSI camera is a PipeWire node (spa-libcamera). Record the mode that opened, whether frames arrived, and whether the producer used one memory block or one per plane. - -Fix only a defect the pass hits. A separate-plane producer belongs to the multi-plane quest. If that is why a Pi produces nothing, write that down and stop. `doc/lib/rs/moq-video.md` says both paths are reachable. If a path cannot capture, correct that sentence in the same change. - -A libcamera source stays out. Embedded video already leaves `rpicam-vid` to the application. This pass uses the PipeWire camera only. - -## Required - -- A sandbox that can show the camera portal dialog, and a Raspberry Pi whose CSI camera appears as a PipeWire node - -## Related - -- [Capture multi-plane PipeWire cameras](/quest/m2/pipewire-camera-planes.md) - separate-plane I420 and NV12, when the pass finds them -- [Video hardware validation](/quest/m3/video-hardware.md) - the other encode and capture runs that still need a machine -- [Embedded video path](/quest/m3/video-embedded.md) - presenting on a Pi, which is a different gap diff --git a/quest/m3/processor-vision.md b/quest/m3/processor-vision.md deleted file mode 100644 index 672181a336..0000000000 --- a/quest/m3/processor-vision.md +++ /dev/null @@ -1,43 +0,0 @@ -# [L] Reference vision worker - -## Goal - -A documented customer-run worker connects to a relay with an external processor -credential, subscribes to a low-resolution video rendition only when demanded, -runs object detection, and publishes frame-correlated bounding boxes as a Hang -contribution. The selected rendition is carried by the source broadcast rather -than referenced from another broadcast. A browser renders the boxes over -uninterrupted source video. - -## Plan - -Build the reference in an integration repository or in-tree example, not as a -hosted moq.pro (downstream) runtime. Keep the model replaceable and the output -contract plain: class, confidence, normalized bounds, source video rendition, -group sequence, and frame ordinal. The proof is about transport and lifecycle, -not model accuracy. - -Provide a five minute local path with a synthetic or recorded source and a -second path against a deployed relay with a real publisher. Demonstrate no -source subscription before output demand, requested rendition selection, -bounded latest-frame processing, clean demand teardown, reconnect, two -interchangeable workers on one logical contribution, token rotation, -revocation, and source withdrawal. Use the processor media contract's source -relation and the current net first-hop identity rules for failover and -reconnect; do not introduce a separate epoch field. Republish at the same -source path with a new source identity and prove stale inference output cannot -attach to the new source. Slow inference must drop stale work rather than -build latency. A fixture with a cross-broadcast rendition reference fails as -unsupported without subscribing to its target. Define refusal of an -unverifiable source relation in the shared contract before implementing it -here, and keep ordinary source playback independent of that refusal. - -Publish deployment examples for a local process and one generic container -platform, without tying anything in-tree to either. Record CPU/GPU and traffic -measurements so a customer can size its own worker. - -## Required - -- [Processor media contract](/quest/m1/processor/media-contract.md) - supplies - the contribution and source-relation schema the example worker must publish -- moq.pro processor registration exists (downstream, external condition) diff --git a/quest/m2/quic-gcc.md b/quest/m3/quic-gcc.md similarity index 91% rename from quest/m2/quic-gcc.md rename to quest/m3/quic-gcc.md index be4ba72d15..ba049a17df 100644 --- a/quest/m2/quic-gcc.md +++ b/quest/m3/quic-gcc.md @@ -25,8 +25,3 @@ than loopback. State the experiment's boundary beside the result: netem cannot establish behavior against production cross traffic or real wifi and cellular loss. - -## Required - -- [Receive timestamps](/quest/m2/quic-receive-ts.md) - the per-packet - arrival times the delay filter runs on diff --git a/quest/m2/quic-receive-ts.md b/quest/m3/quic-receive-ts.md similarity index 85% rename from quest/m2/quic-receive-ts.md rename to quest/m3/quic-receive-ts.md index d36d4c299d..b09d7c93f5 100644 --- a/quest/m2/quic-receive-ts.md +++ b/quest/m3/quic-receive-ts.md @@ -17,7 +17,7 @@ written reason it does not pay. `Controller` that hands each acknowledged packet its receive instant. Both ends are ours; browsers never see it. - Compare the forward delay it measures against the half-RTT estimate the - [deadline quest](/quest/m1/quic/deadline.md) starts with, on the impaired + [deadline quest](/quest/m2/quic-deadline.md) starts with, on the impaired path profile with asymmetric delay. Report how often the half-RTT estimate would have kept a hopeless retransmission or reset a deliverable one. - Measure the ACK overhead the timestamps add on a fanout-shaped relay egress, @@ -25,8 +25,3 @@ written reason it does not pay. The GCC experiment requires this; a delay-based controller without per-packet arrival times is a different, weaker experiment. - -## Related - -- [QUIC GCC](/quest/m2/quic-gcc.md) - the controller that consumes it -- [Per-stream deadlines](/quest/m1/quic/deadline.md) - the other consumer diff --git a/quest/m2/routing-cost-domains.md b/quest/m3/routing-cost-domains.md similarity index 93% rename from quest/m2/routing-cost-domains.md rename to quest/m3/routing-cost-domains.md index 58e0ab5f1f..b22a39c42a 100644 --- a/quest/m2/routing-cost-domains.md +++ b/quest/m3/routing-cost-domains.md @@ -4,7 +4,7 @@ Settle how independently operated MoQ networks exchange reachability without adding incomparable costs, at the cluster boundaries where -[Cluster routing](/quest/m1/cluster-routing.md) keeps path vector with cluster +[Cluster routing](/quest/m1/cluster-routing/README.md) keeps path vector with cluster ids as hops. Cost inside one cluster is cluster-routing's. Produce a reviewed design and scoped implementation quests, not a protocol implementation. Cloudflare, moq.pro, and self-hosted relays can retain their @@ -56,7 +56,5 @@ quests. Open wire/API choices belong to this design exercise. ## Related -- [Cluster routing](/quest/m1/cluster-routing.md) - in-cluster topology and - cost; this designs only what crosses its boundaries - [#3769](https://github.com/moq-dev/moq/pull/3769) - measurement-based pricing prompted the separation of measurement, operator policy, and protocol diff --git a/quest/m2/teleop/browser-package.md b/quest/m3/teleop-browser-package.md similarity index 100% rename from quest/m2/teleop/browser-package.md rename to quest/m3/teleop-browser-package.md diff --git a/quest/m2/teleop/mavlink.md b/quest/m3/teleop-mavlink.md similarity index 100% rename from quest/m2/teleop/mavlink.md rename to quest/m3/teleop-mavlink.md diff --git a/quest/m2/teleop/proof.md b/quest/m3/teleop-proof.md similarity index 86% rename from quest/m2/teleop/proof.md rename to quest/m3/teleop-proof.md index cf45e91441..b3fae97803 100644 --- a/quest/m2/teleop/proof.md +++ b/quest/m3/teleop-proof.md @@ -20,8 +20,3 @@ somebody's webcam. Standing SITL up in CI is separate work with its own build dependencies. Reproducibility by hand is the bar here; automate it later if it proves worth the maintenance. - -## Required - -- [MAVLink bridge](/quest/m2/teleop/mavlink.md) -- [Browser teleoperation package](/quest/m2/teleop/browser-package.md) diff --git a/quest/m2/unity.md b/quest/m3/unity.md similarity index 94% rename from quest/m2/unity.md rename to quest/m3/unity.md index d2acb94f08..5defae3fce 100644 --- a/quest/m2/unity.md +++ b/quest/m3/unity.md @@ -24,4 +24,4 @@ target, subscribes to a broadcast, and plays decoded audio through an ## Required -- [Package](/quest/m2/cs/package.md) - the NuGet whose contents Unity imports +- [Package](/quest/m3/cs/package.md) - the NuGet whose contents Unity imports diff --git a/quest/m2/unreal.md b/quest/m3/unreal.md similarity index 100% rename from quest/m2/unreal.md rename to quest/m3/unreal.md diff --git a/quest/m3/upstream-forks.md b/quest/m3/upstream-forks.md deleted file mode 100644 index 61debd6e69..0000000000 --- a/quest/m3/upstream-forks.md +++ /dev/null @@ -1,62 +0,0 @@ -# [S] Offer the uniffi generator fixes upstream - -## Goal - -Every general fix our uniffi generator forks carry has been offered to its -upstream, or recorded as declined or MoQ-specific, so each fork shrinks -toward a pin on an upstream tag. Very low priority: the forks work, and the -first step is someone else's review. Every external post, issue, or PR needs -the maintainer's approval at the time it is made. - -One outcome per candidate: - -- **uniffi-bindgen-cpp** (`kixelated/uniffi-bindgen-cpp`, forked from - LiveKit's `uniffi-0.31-async`, PR #1; - [#4100](https://github.com/moq-dev/moq/pull/4100)): the two leak fixes (a - ready Rust future freed without `rust_future_complete`, and a dropped - foreign future that never completed its oneshot), the missing - `#include ` MSVC needs, the uniffi 0.32 port, and - `error_style = "expected"`. The bug fixes are the easy offer; the 0.32 port - and the expected style depend on LiveKit taking async at all. -- **uniffi-dart** (`kixelated/uniffi-dart`; - [#4072](https://github.com/moq-dev/moq/pull/4072)): the - `nix/uniffi-dart-record-error.patch` and the RustBuffer release fixes. Fix - the latent `lowerForeignBytes` leak first (borrowed `&[u8]` arguments - allocate a `ForeignBytes` nothing frees; the free belongs after the call, - not inside the lowering), and audit callback interfaces for the same - RustBuffer leak, so the upstream offer is complete. `moq_ffi` uses neither - today. Also the enum `toString()` from an exported `Display` - (kixelated/uniffi-dart#6). -- **uniffi-bindgen-go** (`kixelated/uniffi-bindgen-go`): an enum error's - `Error()` from its exported `Display`, with the `DisplayError` fixture. - The uniffi 0.32 port is NordSecurity/uniffi-bindgen-go#96. -- **uniffi-rs Python typing** of data-carrying enum variants - ([#4049](https://github.com/moq-dev/moq/pull/4049)): the generated type - makes `moq.VideoEncoderKind.AUTO()` fail pyright, so - `doc/lib/py/index.md` carries a `pyright: ignore`. Fix it at the source - and drop the ignore once a release carries it. - -## Plan - -- Decided in #4100: fork tags keep upstream's `v+v` - scheme with a `-kixelated.N` pre-release, e.g. - `v0.11.0-kixelated.1+v0.32.2`, matching the Dart fork. It never collides - with an upstream tag. Under SemVer a pre-release sorts before its base, so - this only works because every pin names the exact tag; never let tooling - pick "the newest" fork tag (Codex on #4307). -- Offer each fix with the regression test it landed with. When upstream - merges one, move the pin in `flake.nix` (and the places its comment lists) - and delete the carried patch. -- Record each outcome (merged, declined with the reason, or not offered - because it is MoQ-specific) in the PR that finishes this quest. - -Public API: none. Wire: none. - -## Required - -- The maintainer approves offering the fixes upstream, per post, issue, or PR - -## Related - -- [Upstream the fork](/quest/m1/quic/upstream.md) - the same practice for the noq fork -- [C++ through moq-ffi](/quest/m1/cpp/README.md) - the line that forked the C++ generator diff --git a/quest/m3/video-hardware.md b/quest/m3/video-hardware.md index c8bf739fab..6fd3453036 100644 --- a/quest/m3/video-hardware.md +++ b/quest/m3/video-hardware.md @@ -32,8 +32,3 @@ come from plain `cuMemAlloc`. That is not a bug any amount of review finds. - Someone with the hardware runs it: an Intel GPU exposing the VAAPI low-power entrypoint, a second render node, a V4L2 capture device with DMA-BUF export, a Windows machine with MJPEG and YUY2 cameras, and a live camera per platform - -## Related - -- [Validate PipeWire cameras on a portal and a Pi](/quest/m3/pipewire-camera-hardware.md) - the camera portal and a Pi CSI node, which are a different machine from this list -- [PipeWire DMA-BUF on KDE](/quest/m3/2893-video-validate-pipewire-dma-buf-capture-on-kde-hardware.md) - the same kind of gate, for screen capture diff --git a/quest/m2/watch-data-sync.md b/quest/m3/watch-data-sync.md similarity index 86% rename from quest/m2/watch-data-sync.md rename to quest/m3/watch-data-sync.md index a8afced6e0..a704971592 100644 --- a/quest/m2/watch-data-sync.md +++ b/quest/m3/watch-data-sync.md @@ -18,7 +18,3 @@ releases it. tracks release every record in order. - Take this up when an application needs synchronized data playback; until then, a raw consumer reads payloads as they arrive. - -## Related - -- [Cross-track correlation](/quest/m2/teleop/correlation.md) - joins recordings on the broadcast clock rather than live playout diff --git a/quest/m2/whep-abr.md b/quest/m3/whep-abr.md similarity index 100% rename from quest/m2/whep-abr.md rename to quest/m3/whep-abr.md diff --git a/quest/m4/README.md b/quest/m4/README.md index 2dbf68dde3..73be8d27bb 100644 --- a/quest/m4/README.md +++ b/quest/m4/README.md @@ -13,7 +13,4 @@ the milestone its priority belongs in. ## Required -- [VAAPI encode and decode](/quest/m4/video-vaapi.md) - H.265 encode and decode, and pre-generated bindings that remove the libclang build dependency, gated on a moq-dev/vaapi release -- [Pool VAAPI resize surfaces](/quest/m4/vaapi-resize-pool.md) - a resize reuses one output surface per size once moq-vaapi ships that pool - [Safari WebTransport](/quest/m4/safari-webtransport.md) - WebKit browsers return to WebTransport once WebKit 319818 ships fixed -- [MSFTS convergence](/quest/m4/msfts-convergence.md) - the demultiplexed TS lane maps onto MSFTS ES-level carriage once msfts#33 settles the payload unit diff --git a/quest/m4/vaapi-resize-pool.md b/quest/m4/vaapi-resize-pool.md deleted file mode 100644 index 6041e66901..0000000000 --- a/quest/m4/vaapi-resize-pool.md +++ /dev/null @@ -1,19 +0,0 @@ -# [S] Pool VAAPI resize surfaces - -## Goal - -A VAAPI resize reuses one output surface per destination size instead of allocating one per frame. `Surface::resize` stays the same call. The decoder pool is unchanged. - -## Plan - -`Processor` in moq-vaapi allocates the blit output with `ExportedFrame::from_surface` on every resize. Keep one surface per output size. When the exported frame drops, that surface returns and the next blit of the same size uses it. A frame the consumer still holds is not overwritten; the processor allocates another. The pool keeps one free surface per size and destroys any surface returned past that, so a burst of released frames cannot park them all. That is the decoder pool's rule, applied to resize outputs. - -moq-video already blits through `Processor` and exports the result. The reuse test belongs in moq-vaapi. Here, bump the workspace requirement and confirm a resize still returns an NV12 DMA-BUF. - -## Required - -- A `moq-vaapi` release whose `Processor` reuses one output surface per destination size and destroys free surfaces past one per size - -## Related - -- [VAAPI encode and decode](/quest/m4/video-vaapi.md) - H.265 and checked-in bindings, the other moq-vaapi gate From a63bb29d2fabf7d76b7e4218769fa56810b6c199 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 30 Sep 2026 07:14:31 -0700 Subject: [PATCH 2/3] quest: apply the 2026-09-30 audit Reorder m0 for Seattle interop, delete quests done on dev, prune and park marginal work, merge overlaps, and add the missing ordering links. Co-Authored-By: Claude Opus 5.5 --- quest/README.md | 17 ++-- quest/m0/README.md | 41 +++++---- quest/m0/audio-jitter-target/README.md | 18 ++-- quest/m0/frame-alloc-budget.md | 2 +- quest/m0/ietf-fin-not-cancel.md | 4 + quest/m0/ietf-legal-input.md | 6 ++ quest/m0/ietf-subgroup-refusal.md | 4 + quest/m0/ietf-uni-stream-types.md | 4 + quest/m0/noq-reassembly-cap.md | 11 ++- quest/m0/qmux-reset-race.md | 4 + quest/m0/quest-check-everywhere.md | 24 ++--- quest/m0/remove-gossip.md | 16 +++- quest/m0/request-caps.md | 4 + quest/m0/shared-fronts.md | 1 + quest/m0/wildcard/README.md | 22 +++-- ...r-a-synchronous-decode-so-the-publisher.md | 53 ++++------- ...ic-tracks-and-preserve-sequences-across.md | 5 ++ ...generated-media-timelines-to-wall-clock.md | 53 ++++++----- quest/m1/README.md | 28 +++--- quest/m1/archive/enrollment-flake.md | 2 +- quest/m1/archive/writer.md | 8 ++ quest/m1/auth/README.md | 7 +- quest/m1/auth/bindings.md | 29 +++--- quest/m1/auth/request-token.md | 4 + quest/m1/auth/token-in-band.md | 7 +- quest/m1/av-clock.md | 3 + quest/m1/bbr-ack-cleanup.md | 9 +- quest/m1/bench-ci.md | 1 + quest/m1/broadcast-epoch/README.md | 6 +- quest/m1/broadcast-epoch/bindings.md | 10 ++- quest/m1/broadcast-epoch/gst-obs.md | 8 +- quest/m1/broadcast-epoch/origin.md | 5 ++ quest/m1/cache-wall-eviction.md | 1 + quest/m1/cli-packaging.md | 14 +++ quest/m1/cli-serve.md | 9 ++ quest/m1/cluster-routing/README.md | 46 +++++++++- quest/m1/color-model.md | 53 +++-------- quest/m1/cpp/README.md | 16 ++-- quest/m1/cross-relay-bursts.md | 4 + quest/m1/dart-codecs.md | 7 ++ quest/m1/dart-publish.md | 6 +- quest/m1/data-track-clock.md | 3 + quest/m1/datagram-unfetchable.md | 11 +++ quest/m1/drain/README.md | 4 + quest/m1/ffi-shape/net.md | 10 ++- quest/m1/frame-slot-charge.md | 4 + quest/m1/hop-aligned-import.md | 6 +- quest/m1/js-fetch.md | 4 + quest/m1/legacy-end-overshoot.md | 22 +++-- quest/m1/merge-queue.md | 13 +-- quest/m1/mobile-ownership.md | 36 ++++---- quest/m1/moxygen/fetch.md | 4 + quest/m1/obs-moq-video/README.md | 14 ++- quest/m1/obs-moq-video/linux-bundle.md | 6 +- quest/m1/obs-moq-video/source.md | 3 +- quest/m1/obs-moq-video/vpx.md | 2 +- quest/m1/origin-front-parks.md | 8 ++ quest/m1/parked-read-wakes.md | 8 ++ ...relay-cpu-is-vdso-clock-reads-the-drive.md | 2 +- quest/m1/perf/README.md | 23 +++-- quest/m1/perf/group-cost.md | 15 ++++ quest/m1/perf/lock-wait.md | 4 + quest/m1/perf/uring-one-enter.md | 2 + quest/m1/perf/uring-quiescence.md | 16 ++-- quest/m1/performance-comparisons.md | 15 ++-- quest/m1/plan-watch-worker.md | 1 + quest/m1/quic/README.md | 29 ++++-- quest/m1/quic/reliable-reset.md | 2 + quest/m1/quic/scheduler.md | 2 + quest/m1/quic/upstream.md | 16 ++-- quest/m1/quic/uring-close.md | 6 ++ quest/m1/raw-stream-codes.md | 7 +- quest/m1/relay-auth-client-ca.md | 8 +- quest/m1/relay-iroh-opt-in.md | 5 ++ quest/m1/resume-latest.md | 2 + quest/m1/rs2ts/README.md | 21 +++-- quest/m1/rs2ts/sans-io/README.md | 8 +- quest/m1/stats/README.md | 29 ++---- quest/m1/subscribe-drop.md | 13 ++- quest/m1/test-flakes-2.md | 90 ------------------- quest/m1/test-flakes-2/README.md | 46 ++++++++++ quest/m1/test-flakes-2/broadcast-race.md | 22 +++++ quest/m1/test-flakes-2/cli-paused-clock.md | 30 +++++++ quest/m1/test-flakes-2/media-late-join.md | 15 ++++ quest/m1/test-flakes-2/mux-debounce-clock.md | 15 ++++ quest/m1/test-flakes-2/publish-audio-clock.md | 11 +++ .../shaper-virtual-time.md | 0 quest/m1/test-flakes-2/subscription-cut.md | 14 +++ quest/m1/test-flakes-2/warn-capture.md | 14 +++ .../m1/test-flakes-2/websocket-paused-tls.md | 14 +++ quest/m1/track-priority-scope.md | 7 ++ quest/m1/track-tail-interop.md | 18 ++-- quest/m1/transport-upgrade/README.md | 2 +- quest/m1/uring-ietf.md | 5 ++ quest/m1/watch-worker.md | 1 + quest/m1/wt-close-upstream.md | 9 ++ ...evc-and-av1-support-in-the-nvidia-codec.md | 22 ++--- ...te-35-ad-cue-signaling-carried-opaquely.md | 6 ++ ...ipewire-dma-bufs-safely-into-the-vulkan.md | 10 ++- ...-the-webtransport-stream-header-at-open.md | 8 ++ ...tch-completion-wakeups-with-min-timeout.md | 3 + ...fixed-file-slots-for-worker-udp-sockets.md | 3 + quest/m2/README.md | 27 +++--- quest/m2/announce-shapes.md | 15 ++-- quest/m2/archive-browser.md | 6 +- quest/m2/archive-paced-replay.md | 6 ++ quest/m2/audio-decode-mediacodec.md | 1 + quest/m2/audio-decode-mediafoundation.md | 4 + quest/m2/audio-encode-mediacodec.md | 1 + quest/m2/audio-opus-backend.md | 25 +++++- quest/m2/av1-metadata.md | 6 ++ quest/m2/bench-coverage.md | 34 +++++-- quest/m2/browser-media-qa-engines.md | 10 ++- quest/m2/cache-shard.md | 5 ++ quest/m2/captions-cea.md | 8 ++ quest/m2/capture-windows.md | 8 ++ quest/m2/catalog-tracks.md | 56 +++++------- quest/m2/closure-counters.md | 2 + quest/m2/color-catalog.md | 56 ++++++++++++ quest/m2/cpp-vcpkg.md | 4 + quest/m2/emsg.md | 5 ++ quest/m2/flate.md | 36 ++++++-- quest/m2/flv-script.md | 2 + quest/m2/gop-overhead.md | 4 + quest/m2/id3.md | 7 ++ quest/m2/intra-refresh/README.md | 19 ++-- quest/m2/ladder/README.md | 3 + quest/m2/mobile-capture-android.md | 4 + quest/m2/mobile-capture-ios.md | 4 + quest/m2/mobile-completion.md | 1 + quest/m2/moq-install-url.md | 3 + quest/m2/moq-installer.md | 78 ++++++---------- quest/m2/moq-relay-subcommand.md | 3 + quest/m2/msfts-convergence.md | 26 +++--- quest/m2/nvenc-pool.md | 8 +- quest/m2/obs-decode-linux.md | 4 + quest/m2/one-port/README.md | 10 ++- quest/m2/one-port/udp-demux.md | 34 ++++--- quest/m2/p2p/README.md | 7 +- quest/m2/p2p/cost-scopes.md | 4 + quest/m2/p2p/transit.md | 40 ++++----- quest/m2/p2p/webrtc.md | 1 + quest/m2/processor/README.md | 3 + quest/m2/processor/grant-lease.md | 4 +- quest/m2/processor/media-contract.md | 4 + quest/m2/quic-ack-hook.md | 33 +++++-- quest/m2/quic-ack-progress.md | 12 ++- quest/m2/quic-bbr-loss-parity.md | 4 + quest/m2/quic-bbr-natural-drain.md | 44 ++++++++- quest/m2/quic-deadline.md | 6 ++ quest/m2/quic-ecn-measure.md | 3 + quest/m2/quic-ecn.md | 5 ++ quest/m2/quic-egress-profile.md | 67 ++++++++++---- quest/m2/quic-keep-alive.md | 10 ++- quest/m2/quic-probe.md | 1 + quest/m2/quic-qmux.md | 15 +++- quest/m2/relay-io-uring-package.md | 4 +- quest/m2/rs2ts-ietf.md | 4 + quest/m2/rs2ts-sans-io-ietf.md | 3 + quest/m2/sei.md | 43 ++++++--- quest/m2/signed-priority.md | 5 ++ quest/m2/starvation-frames.md | 6 ++ quest/m2/stats-delta.md | 5 ++ quest/m2/stats-encoder-feedback.md | 22 +++++ quest/m2/teleop/README.md | 32 +++++-- quest/m2/teleop/robot.md | 22 +++++ quest/m2/text-schema.md | 2 + quest/m2/uring-open-contract.md | 8 ++ quest/m2/uring-tcp/README.md | 9 +- quest/m2/video-codec-coverage.md | 1 + quest/m2/video-vaapi.md | 26 ++++-- quest/m2/x11-capture-shm.md | 9 +- ...use-sendmsg-zc-for-large-udp-gso-trains.md | 4 + ...ter-tx-pool-buffers-for-zero-copy-sends.md | 8 ++ quest/m3/README.md | 11 ++- quest/m3/af-xdp.md | 8 +- quest/m3/cat/README.md | 5 ++ quest/m3/cat/present.md | 6 +- quest/m3/cpp-conan.md | 3 + quest/m3/cs/README.md | 4 + quest/m3/hidden-exemption.md | 5 +- quest/m3/intra-refresh-bindings.md | 1 + quest/m3/intra-refresh-encode-config.md | 6 ++ quest/m3/intra-refresh-nvenc.md | 12 ++- quest/m3/intra-refresh-v4l2.md | 4 + quest/m3/livekit-shim.md | 14 +-- quest/m3/nvenc-av1.md | 31 +++++++ quest/m3/obs-linux-gpu.md | 4 + quest/m3/quic-gcc.md | 10 +++ quest/m3/quic-receive-ts.md | 9 ++ quest/m3/routing-cost-domains.md | 4 + quest/m3/teleop-browser-package.md | 2 + quest/m3/teleop-mavlink.md | 2 + quest/m3/teleop-proof.md | 7 ++ quest/m3/unity.md | 2 + quest/m3/unreal.md | 4 +- quest/m3/video-hardware.md | 67 ++++++++++++-- quest/m3/watch-data-sync.md | 9 +- quest/m3/whep-abr.md | 3 + quest/m4/safari-webtransport.md | 2 +- 200 files changed, 1820 insertions(+), 737 deletions(-) delete mode 100644 quest/m1/test-flakes-2.md create mode 100644 quest/m1/test-flakes-2/README.md create mode 100644 quest/m1/test-flakes-2/broadcast-race.md create mode 100644 quest/m1/test-flakes-2/cli-paused-clock.md create mode 100644 quest/m1/test-flakes-2/media-late-join.md create mode 100644 quest/m1/test-flakes-2/mux-debounce-clock.md create mode 100644 quest/m1/test-flakes-2/publish-audio-clock.md rename quest/m1/{ => test-flakes-2}/shaper-virtual-time.md (100%) create mode 100644 quest/m1/test-flakes-2/subscription-cut.md create mode 100644 quest/m1/test-flakes-2/warn-capture.md create mode 100644 quest/m1/test-flakes-2/websocket-paused-tls.md create mode 100644 quest/m2/color-catalog.md create mode 100644 quest/m3/nvenc-av1.md diff --git a/quest/README.md b/quest/README.md index dcc3e3b52d..e4b9dbdde2 100644 --- a/quest/README.md +++ b/quest/README.md @@ -7,18 +7,19 @@ grouped into milestones ordered by priority. ## Plan -m0 is everything in flight now: announce and wildcard routing, and audio -playout (jitter target, quality harness, A/V clock). m1 is the next wave across reliability, features, -performance, and planning. m2 holds later features, design studies, and -experiments. m3 is deferred: work whose first step is outside this repository. -m4 waits on an upstream release or external dependency to ship. Priority is +m0 is everything in flight now: relay hardening and IETF interop ahead of +Seattle, wildcard routing, and audio playout (jitter target and quality +harness). m1 is the next wave across reliability, features, performance, and +planning. m2 holds later features, design studies, and experiments. m3 is +gated on the outside world: hardware, a partner, a consumer, or a provider's +offer. m4 waits on an upstream release. Priority is separate from branch targeting: published API and wire breaks still land on dev under the repository rules. ## Required -- [m0: immediate priorities](/quest/m0/README.md) - everything in flight now: announce and wildcard routing, and audio playout +- [m0: immediate priorities](/quest/m0/README.md) - everything in flight now: relay hardening and IETF interop for Seattle, wildcard routing, and audio playout - [m1: next wave](/quest/m1/README.md) - reliability, capabilities, performance, and the planning that settles their contracts - [m2: later work](/quest/m2/README.md) - deferred features, design studies, and experiments -- [m3: deferred](/quest/m3/README.md) - gated on the outside world: hardware nobody has, a partner, or a provider's offer -- [m4: upstream](/quest/m4/README.md) - waiting on an upstream release or external dependency, re-checked periodically +- [m3: deferred](/quest/m3/README.md) - gated on the outside world: hardware, a partner, a consumer, or a provider's offer +- [m4: upstream](/quest/m4/README.md) - waiting on an upstream release, re-checked periodically diff --git a/quest/m0/README.md b/quest/m0/README.md index 899dc38ab3..fae3a99c49 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -2,14 +2,13 @@ ## Goal -The work in flight now, in three independent tracks. Relay hardening: every -resource a peer can make the relay hold is bounded by what it sent or by a -budget, no peer input panics the process, and legal moq-transport input never -fails a session, ahead of Seattle interop on 2026-10-12. Routing: a publisher stops -sending announce updates the wire cannot tell apart, a service claims the -prefix it could serve instead of enumerating broadcasts. Audio playout: the target is a measured -estimate of arrival timing in both languages, a browser regression fails a -nightly run, and the audio playhead becomes the clock video follows. +The work in flight now, in three independent tracks. Relay hardening: legal +moq-transport input never fails a session ahead of Seattle interop on +2026-10-12, every resource a peer can make the relay hold is bounded by what +it sent or by a budget, and no peer input panics the process. Routing: a +service claims the prefix it could serve instead of enumerating broadcasts. +Audio playout: the target is a measured estimate of arrival timing in both +languages, and a browser regression fails a nightly run. ## Plan @@ -18,19 +17,25 @@ done. moq.pro tracks this repository as a submodule rather than a release, so no release quest gates this milestone. The Pronto GPU integration lives in moq.pro. -Routing: announce-update dedupe is a wire-compatible fix on every version. The -wildcard line is prefix-only on the wire; its resolve and demand work is done -on the line branch and waits to land. Serving the relay's ingested-only -view (`origin::Consumer::local()`) to localhost workers belongs to moq.pro's -edge, which embeds moq-relay; it moved there on 2026-09-28. +Relay hardening: IETF interop leads the ranking, since only those quests +block Seattle. Subgroup refusal came from the moxygen line and IETF stream +types from m1; both moved here in the 2026-09-30 audit because a session +ended by legal input is exactly what Seattle would hit. The DoS hardening +from an external review on 2026-09-29, verified against `main`, stays in m0 +as security work. Its quests describe fixes, not exploits. + +Routing: the wildcard line is prefix-only on the wire; its resolve and demand +work is done on the line branch and waits to land. Serving the relay's +ingested-only view (`origin::Consumer::local()`) to localhost workers belongs +to moq.pro's edge, which embeds moq-relay; it moved there on 2026-09-28. Audio playout: the jitter target replaces the round-trip guess. The harness's browser lane grades it nightly and records the traces it replays; the native -lane is a standalone m1 quest, since nothing here waits on it. The A/V clock -builds on the jitter target's per-track spread. - -Relay hardening comes from an external review on 2026-09-29, verified against -`main`. Its quests describe fixes, not exploits. +lane is a standalone m1 quest, since nothing here waits on it. The harness +line lands before the jitter line, since both add +`js/watch/src/audio/replay.test.ts`. The [A/V clock](/quest/m1/av-clock.md) +moved to m1 in the 2026-09-30 audit: it waits on the whole jitter line and is +a published `@moq/watch` break on dev. Published API or wire breaks still land on dev; each quest's Plan says so. diff --git a/quest/m0/audio-jitter-target/README.md b/quest/m0/audio-jitter-target/README.md index 5e99fd83fb..9b5c88ad7d 100644 --- a/quest/m0/audio-jitter-target/README.md +++ b/quest/m0/audio-jitter-target/README.md @@ -14,7 +14,7 @@ the same arrival trace. Boundaries: convergence still uses skip-ahead and silence, so playing slightly faster or slower to converge stays [Time stretch](/quest/m1/watch-audio-time-stretch.md). No packet loss concealment. -Video keeps its own target; making the audio playhead the clock is [Plan: A/V +Video keeps its own target; making the audio playhead the clock is [A/V clock](/quest/m1/av-clock.md). ## Plan @@ -42,15 +42,15 @@ reporter; they replace the #3477 traces wherever the quests name them. The algorithm is written down at `doc/concept/audio-jitter.md`, with a conformance corpus beside it that both implementations will read. -Neither `main` nor `dev` has a measured estimator. `js/watch/src/sync.ts:159` +Neither `main` nor `dev` has a measured estimator yet. `js/watch/src/sync.ts:159` still computes `max(MIN_JITTER, minRtt * 1.25)` from the connection's PROBE, and `js/watch/src/audio/latency.ts` still exists. `sync.ts` also adds the advertised jitter to that term, where the document settles on a maximum. -The prior art is the branch of PR #3517, -`origin/quest/m0/3477-watch-auto-latency`, two commits ahead of `dev`. The PR -is closed and never merged; the watch quest starts from the branch rather than -from `dev`. It already deletes the RTT term +The prior art from PR #3517 (closed 2026-09-10, branch deleted 2026-09-30) +landed on this line's branch through +[#3954](https://github.com/moq-dev/moq/pull/3954), so the watch quest works +there. It deletes the RTT term (`MIN_JITTER`, `FALLBACK_JITTER`, `#minRtt`, and the `probe` input are gone from `sync.ts`; `latency.ts` survives, minus `reanchorFloor`) and plumbs a per-track arrival `spread` through `Container.Consumer`, measured at container @@ -66,7 +66,7 @@ is immediate and unclamped, so a tune-in across a stale group sets the target to seconds. Note that `sync.ts` has since been refactored on `main` to a `register(jitter)` -list, so the branch does not rebase cleanly. +list, which is one of the conflicts merging `main` in resolves. Native has no jitter buffer at all. `rs/moq-audio`'s `decode::Options` (`rs/moq-audio/src/decode/consumer.rs`) carries `max_age`, how far @@ -76,7 +76,8 @@ buffer against uneven arrivals. ## Required -- [Watch](/quest/m0/audio-jitter-target/watch.md) - js/watch and js/hang bring the #3517 branch's estimator into conformance +- [Audio quality harness](/quest/m0/audio-quality-harness/README.md) - lands first, since both lines add `js/watch/src/audio/replay.test.ts`; it also records the traces the watch quest replays +- [Watch](/quest/m0/audio-jitter-target/watch.md) - js/watch and js/hang bring the #3954 estimator into conformance - [Native](/quest/m0/audio-jitter-target/native.md) - rs/moq-audio grows a measured jitter buffer from the same algorithm ## Closes @@ -85,5 +86,4 @@ buffer against uneven arrivals. ## Related -- [Audio quality harness](/quest/m0/audio-quality-harness/README.md) - the automated proof, and the recorder of the traces the watch quest replays - [Time stretch](/quest/m1/watch-audio-time-stretch.md) - inaudible convergence, on top of this diff --git a/quest/m0/frame-alloc-budget.md b/quest/m0/frame-alloc-budget.md index bd55c8a8e4..25c28fba88 100644 --- a/quest/m0/frame-alloc-budget.md +++ b/quest/m0/frame-alloc-budget.md @@ -28,5 +28,5 @@ config, not moq-net. Wire: none. ## Related -- [Frame slot charge](/quest/m1/frame-slot-charge.md) - also changes what a group charges the cache +- [Frame slot charge](/quest/m1/frame-slot-charge.md) - lands first; both change the cache charge in `model/group.rs` - [Peer limits](/quest/m1/quic/peer-limits.md) - stream counts and windows per peer diff --git a/quest/m0/ietf-fin-not-cancel.md b/quest/m0/ietf-fin-not-cancel.md index 0f1d880919..ccae4e3d28 100644 --- a/quest/m0/ietf-fin-not-cancel.md +++ b/quest/m0/ietf-fin-not-cancel.md @@ -26,6 +26,10 @@ silently ends the subscription. Public API: none. Wire: conformance fix; no draft change. +## Required + +- [Legal IETF input](/quest/m0/ietf-legal-input.md) - lands first; both change `ietf/fetch.rs` and the request close path + ## Related - [Lite request streams](/quest/m1/request-stream-serve.md) - lite deliberately treats a FIN as ending the request; don't unify the two diff --git a/quest/m0/ietf-legal-input.md b/quest/m0/ietf-legal-input.md index c6f761deb2..c90b5f547b 100644 --- a/quest/m0/ietf-legal-input.md +++ b/quest/m0/ietf-legal-input.md @@ -41,3 +41,9 @@ choice, since honoring them would change what existing peers receive. - Mirror the decode in `js/net`. Public API: none. Wire: fixes conformance; no draft change. + +## Related + +- [Request token](/quest/m1/auth/request-token.md) - also edits `decode_params!`, turning the ignored token into a per-request grant +- [IETF FIN semantics](/quest/m0/ietf-fin-not-cancel.md) - the other interop blocker +- [IETF stream types](/quest/m0/ietf-uni-stream-types.md) - same stream-scoped-before-fatal rule for uni streams diff --git a/quest/m0/ietf-subgroup-refusal.md b/quest/m0/ietf-subgroup-refusal.md index 06fd256b07..969ea14338 100644 --- a/quest/m0/ietf-subgroup-refusal.md +++ b/quest/m0/ietf-subgroup-refusal.md @@ -17,6 +17,10 @@ peer is at fault, say so on its tracker and keep a regression test for our side. A test with an IETF peer that sends a subgroup 1 stream next to a healthy track is the check. +Moved from the moxygen line to m0 in the 2026-09-30 audit: a non-zero +subgroup ending the upstream session is exactly m0's "legal input never fails +a session", and moxygen will send it at Seattle interop on 2026-10-12. + ## Related - [Moxygen compatibility](/quest/m1/moxygen/README.md) - subgroups stay out of scope; only the blast radius is in diff --git a/quest/m0/ietf-uni-stream-types.md b/quest/m0/ietf-uni-stream-types.md index 6b0ca089c9..1dfd4e0104 100644 --- a/quest/m0/ietf-uni-stream-types.md +++ b/quest/m0/ietf-uni-stream-types.md @@ -8,6 +8,10 @@ so it ships on main. ## Plan +Moved from m1 to m0 in the 2026-09-30 audit: a padding stream answered with +INTERNAL_ERROR is legal input mishandled, which m0 fixes before Seattle +interop on 2026-10-12. + `run_unis` in `rs/moq-net/src/ietf/session.rs` routes every non-SETUP uni stream to `run_uni_group`, which rejects padding and unknown types alike while leaving the session alive. That stream-only rejection reaches the wire as diff --git a/quest/m0/noq-reassembly-cap.md b/quest/m0/noq-reassembly-cap.md index ae7d7e9e4b..160d9c08f7 100644 --- a/quest/m0/noq-reassembly-cap.md +++ b/quest/m0/noq-reassembly-cap.md @@ -16,14 +16,19 @@ the crates are renamed. RUSTSEC-2026-0185) to moq-dev/noq: `Assembler::insert` returns an error past 1024 buffered chunks after defragmenting, the stream path closes with `INTERNAL_ERROR`, and the CRYPTO path does the same. Keep quinn's test. - Release 1.3.3 and 2.0.1, then bump the pins here. + 1.3.3 and 2.0.1 are already taken by the open release PRs moq-dev/noq#21 + and #22: fold the port into those releases or take the next patch numbers, + then bump the pins here. - Open the same port as a PR on n0-computer/noq (approved 2026-09-29; the advisory is public). iroh builds stay on the unfixed upstream crate until n0 releases it; bump when they do. - Give `moq-tokio` a finite default connection `receive_window` instead of the backend's `VarInt::MAX` (`rs/moq-tokio/src/noq.rs` `apply_windows`, `quic.rs`). Pick the value with a throughput measurement, not a - guess, and update `doc/bin/relay/config.md`. + guess, and update `doc/bin/relay/config.md`. Size it so relay-to-relay + cluster sessions, which carry every viewer's traffic on one connection, + are not throttled; per-peer windows wait for + [Peer limits](/quest/m1/quic/peer-limits.md). - `rs/moq-uring` depends on `moq-noq-proto` too, so the same pin bump covers the io_uring workers. @@ -31,4 +36,4 @@ Public API: none. Wire: a peer that exceeds the chunk cap is closed. ## Related -- [Peer limits](/quest/m1/quic/peer-limits.md) - per-peer windows and stream limits +- [Peer limits](/quest/m1/quic/peer-limits.md) - per-peer windows and stream limits, which later let cluster sessions take a larger window than clients diff --git a/quest/m0/qmux-reset-race.md b/quest/m0/qmux-reset-race.md index 901b63dae0..0aa6adcdd3 100644 --- a/quest/m0/qmux-reset-race.md +++ b/quest/m0/qmux-reset-race.md @@ -21,3 +21,7 @@ no-op, not a panic. here. Moving to 0.6 is not part of this quest. Public API: none. Wire: none. + +## Related + +- [qmux on noq-proto](/quest/m2/quic-qmux.md) - replaces these stream maps entirely, later diff --git a/quest/m0/quest-check-everywhere.md b/quest/m0/quest-check-everywhere.md index c62ab16a6f..447eadfca6 100644 --- a/quest/m0/quest-check-everywhere.md +++ b/quest/m0/quest-check-everywhere.md @@ -7,21 +7,13 @@ ## Plan -- `dev` has no `quest` flake input and no `quest check` in its justfile; - #4428's main-into-dev sync brings both. After it lands, convert the - old-format quests on `dev` until `quest check` passes there. -- Line branches pin their own `quest` revision (the wildcard line pinned - 46d7fe8 against main's 8590d2a), so an old pin passes an old format. - Merging `main` into each active line bumps the pin; do that for the lines - that fail today and fix what the new check reports. -- `just ci check` runs `quest check` on pull requests only. Also run it on - push to `main`, `dev`, and `quest/**`, so a direct merge commit (such as - `main` merged into a line) can't land a broken tree. Use a dedicated job - that runs `quest check` unconditionally: `check.yml`'s scope steps diff - against `origin/$GITHUB_BASE_REF`, which is empty on a push. +- `check.yml` runs `quest check` on pull requests only. Also run it on push to + `main`, `dev`, and `quest/**`, so a direct merge commit (such as `main` + merged into a line) can't land a broken tree. Use a dedicated job that runs + `quest check` unconditionally: `check.yml`'s scope steps diff against + `origin/$GITHUB_BASE_REF`, which is empty on a push. +- `dev` already pins main's `quest` (8590d2a) and passes since #4428. Only the + wildcard line still pins 46d7fe8: merge `main` into it to bump the pin, and + fix what the new check reports. Public API: none. Wire: none. - -## Required - -- #4428 merged to `dev` diff --git a/quest/m0/remove-gossip.md b/quest/m0/remove-gossip.md index cf99b23543..ecf38db9b7 100644 --- a/quest/m0/remove-gossip.md +++ b/quest/m0/remove-gossip.md @@ -23,11 +23,21 @@ through `--cluster-connect-api` and never enables gossip. - Lands on `main` as a security fix, not `dev`. `--cluster-mesh`, `MOQ_CLUSTER_MESH` and `mesh = true` fail at startup with a message pointing at `cluster.connect`, so nobody silently loses their mesh. - Decided 2026-09-29. + Decided 2026-09-29. moq-cli reuses moq-relay's cluster config, so the same + refusal covers `moq`'s `--cluster-mesh` (`doc/bin/cli.md`). +- `rs/moq-net/tests/mesh_withdraw.rs` stays: it already wires origins to + each other directly (`MockPair`), which is what a configured full mesh + does, and its withdraw semantics still apply. Rewrite it only if gossip + removal changes what it exercises. Decided in the 2026-09-30 audit. - Docs: rewrite the Discovery section of `doc/bin/relay/cluster.md`, drop the `.internal/` warning, and add an upgrade note in `doc/setup/upgrade.md`. - Search the repo for `cluster-mesh` and `mesh = true` in examples and demo - recipes. + Fix the mesh examples in `doc/bin/cli.md`, `doc/bin/relay/config.md`, and + `rs/moq-relay/README.md`, then search the repo for `cluster-mesh` and + `mesh = true` in any other examples and demo recipes. Public API: removes a relay config field and flag. Wire: relays stop announcing `.internal/origins`. + +## Related + +- [Cluster routing](/quest/m1/cluster-routing/README.md) - takes its topology from configured links only diff --git a/quest/m0/request-caps.md b/quest/m0/request-caps.md index b469c58adc..d0ee8679a2 100644 --- a/quest/m0/request-caps.md +++ b/quest/m0/request-caps.md @@ -34,3 +34,7 @@ Announces and subscriptions per session are capped as well. Public API: possibly caps on the session config; propose the shape in the PR. Wire: behaviour within the drafts' existing limits. No format change. + +## Required + +- [IETF FIN semantics](/quest/m0/ietf-fin-not-cancel.md) - MAX_REQUEST_ID refills as requests close, and that change decides when a request closes diff --git a/quest/m0/shared-fronts.md b/quest/m0/shared-fronts.md index 21d5a8af70..272681e48c 100644 --- a/quest/m0/shared-fronts.md +++ b/quest/m0/shared-fronts.md @@ -30,3 +30,4 @@ Public API: none. Wire: none. - [Front deadlines](/quest/m1/front-deadline-index.md) - per-front cost per track - [Front parking](/quest/m1/origin-front-parks.md) - also changes what mints a front +- [Wildcard](/quest/m0/wildcard/README.md) - its line rewrites `model/origin.rs` heavily (+401 lines, fronts end on a standing refusal); land after it or rebase onto it diff --git a/quest/m0/wildcard/README.md b/quest/m0/wildcard/README.md index 334622caa2..0b106cd870 100644 --- a/quest/m0/wildcard/README.md +++ b/quest/m0/wildcard/README.md @@ -22,9 +22,9 @@ over FETCH wants to say "if nobody is publishing this live, I have it", which is the catch-all `**`, a claim about every path at once. The cost of enumerating is real even though its last measurement is stale. -[relay-memory](/quest/m1/relay-memory.md) measured one announcement at 8.8 KB -per relay plus 4.3 KB per additional route before prefix routes made a -standby route a table entry, and owns remeasuring it. Whatever the current +One announcement measured 8.8 KB per relay plus 4.3 KB per additional route +before prefix routes made a standby route a table entry; +[Cluster routing](/quest/m1/cluster-routing/README.md) owns remeasuring it. Whatever the current number, every relay that hears an announcement pays it whether or not anything there subscribes, so "workers times broadcasts" is that number multiplied across the fleet in resident memory. @@ -51,8 +51,8 @@ Route cost already names this case: "The original publisher seeds it with its production cost: zero for a live publish, something large for a standby that would have to start working (a cold transcoder)" (`drafts/draft-lcurley-moq-lite.md`). `moq_auth::Claims.publish` and -`origin::Producer` gain versioned patterns through -[Path patterns](/quest/m1/path-patterns.md), so tokens and filters reuse the +`origin::Producer` gained versioned patterns on the +[Auth](/quest/m1/auth/README.md) line, so tokens and filters reuse the same matcher; advertisements stay prefixes. `Cost { warm, cold }` (`rs/moq-net/src/model/origin.rs:426`) is the route cost since [#2925](https://github.com/moq-dev/moq/pull/2925). @@ -88,8 +88,8 @@ field. ### Decisions - **One prefix on the wire, one pattern in the token and the filter.** An - advertisement is a path prefix; the [path-patterns](/quest/m1/path-patterns.md) - dialect is what tokens and the consume-side filter use, matched by the + advertisement is a path prefix; the pattern dialect from the + [Auth](/quest/m1/auth/README.md) line is what tokens and the consume-side filter use, matched by the shared matcher, so nothing resembles a second grammar and nothing on the wire spells a wildcard. - **Longest prefix wins, and its refusal is final.** This is the rule @@ -189,7 +189,7 @@ field. - **Patterns are independent of clustering.** The `moq-pattern` crate owns the matching semantics tokens and filters share, with no draft of its own; no announce message carries a pattern on either protocol (AUTH grants on - lite-06 do, per [Path patterns](/quest/m1/path-patterns.md)). moq-cluster adds hop + lite-06 do, per the [Auth](/quest/m1/auth/README.md) line). moq-cluster adds hop lists, costs, pool selection, and request resolution to prefix advertisements. @@ -206,7 +206,9 @@ never see `.pro/` broadcasts, which could confuse their business logic. Those versions cannot opt into hidden routes, so the relay never announces them there. Hidden routes are a moq-lite-07 feature, so the player's covering check (Demand, done on the line branch) opts into them and sees a claim only when -lite-07 is negotiated. A customer who wants transcodes upgrades, or +lite-07 is negotiated. Finalizing lite-07 is a rollout condition, not a +blocker for this line (decided in the 2026-09-30 audit), since the check works +whenever lite-07 is negotiated. A customer who wants transcodes upgrades, or subscribes to the explicit `.pro//...` path, which works on any version. Grants and metering are the deployment's; moq.pro's are in its [wildcard questline](https://github.com/moq-dev/moq.pro/blob/main/quest/m2/wildcard/README.md). @@ -223,3 +225,5 @@ distinguish recording generations reads the catalog's archive entry catalog names the generations a claim cannot - [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - derived output mirrors the source path, `@` segment included +- [Announcement shapes](/quest/m2/announce-shapes.md) - moq-lite-only exact, + suffix, and prefix+suffix claims that survive relay hops diff --git a/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md b/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md index 9f04697e6c..0c36f01a0b 100644 --- a/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md +++ b/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md @@ -1,44 +1,27 @@ -# [M] js/net: decode messages synchronously from buffered bytes +# [XS] js/net: cap the publisher's subscription controls ## Goal -The lite publisher applies every buffered control before it pops a group, -with no read-ahead queue: bounded memory and exact control-first ordering at -once. +A peer flooding `SUBSCRIBE_UPDATE` while the lite publisher is blocked in a +control-stream write cannot grow its memory without bound: past a fixed cap +the session fails with a protocol error. ## Plan -The serving loop must apply every buffered `SUBSCRIBE_UPDATE` before it pops a -group, or a group goes out under a range the peer already superseded. Rust gets -that from `poll_decode_maybe` in `rs/moq-net/src/lite/publisher.rs`, which -decodes straight out of the reader's buffer and so drains controls to -exhaustion in one poll. `js/net/src/lite/publisher.ts` works around -the async `Reader` by decoding ahead into `SubscriptionControls`, which the -loop drains synchronously. That queue is unbounded: it grows while the loop is -blocked in a control-stream write, so a peer flooding updates during a stalled -write converts flow-controlled bytes into heap objects. A single-message slot -was tried in #2820 and broke ordering, because `take()` cannot yield to the -decoder without letting a group pop slip in between. - -`Reader` in `js/net/src/stream.ts` already decodes synchronously: a decode is -a function over a `Cursor`, whose reads throw an internal short signal when the -buffered bytes run out. `tryDecode` returns undefined and consumes nothing in -that case, and `decode`/`decodeMaybe` are the one async driver that fills and -retries. The primitives (`u62`, `u53`, `read`, `string`, ...) are that driver -applied to the `Cursor` reads, and the group and FETCH frame loops drain every -buffered frame with `tryDecode` (`js/net/bench/frames.ts`). The 22 -`static async decode` message decoders under `js/net/src/lite/`, plus four -`decodeMaybe` variants, still await a primitive per field. - -- Convert all 26 decoders to a single synchronous body over a `Cursor`, with - the async form as `reader.decode(...)` rather than a second copy. `Message` - in `lite/message.ts` becomes a sync size-prefixed wrapper. -- The publisher drains controls synchronously in its loop and - `SubscriptionControls` goes away. -- Tests: the publisher applies N buffered updates before the next group - pop; a partial update with a group already ready waits for the second fill - and pops the group under the new range, so incomplete is never read as "no - control pending"; the flood case stays bounded. +`SubscriptionControls` in `js/net/src/lite/publisher.ts` (:169) decodes +ahead of the serving loop so every buffered update applies before the next +group pop. It keeps only the newest range for the loop, but the decoder runs +on its own and calls `apply` for each update, so a peer can turn +flow-controlled bytes into unbounded work and heap while the loop is stalled. +First confirm what still grows on the current code, then cap the updates +decoded between two drains by the loop and fail the session on overflow, +matching Rust's refusal of malformed input. Test: a flood during a stalled +write fails the session instead of growing. + +Decided in the 2026-09-30 audit: the rewrite to synchronous decoders is +dropped. Generated lite from the [rs2ts line](/quest/m1/rs2ts/README.md) +replaces hand-written js/net, and it gets control-first ordering from moq-net's +`poll_decode_maybe`. This quest only closes the memory hole until then. ## Closes diff --git a/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md b/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md index 5369139899..886144d2d0 100644 --- a/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md +++ b/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md @@ -86,3 +86,8 @@ the closed producer's cache or terminal state. ## Closes - [#2991](https://github.com/moq-dev/moq/issues/2991) - close this issue when the quest finishes + +## Related + +- [Resume latest](/quest/m1/resume-latest.md) - edits `resume.rs` and its takeover tests (#4491) +- [Parked reads wake](/quest/m1/parked-read-wakes.md) - edits the same `resume.rs` wakeups diff --git a/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md b/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md index 0df856a18a..b12adcc540 100644 --- a/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md +++ b/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md @@ -1,37 +1,36 @@ -# [M] GStreamer preserves the broadcast PTS-to-wall clock +# [S] GStreamer picks the broadcast wall epoch ## Goal -`moqsink` maps every media pad onto one continuous broadcast clock, exposing -its fixed wall epoch through the shared Hang contract. Source restarts never -change the interpretation of media already published. +`moqsink` exposes one fixed wall epoch for the broadcast through the shared +Hang clock contract, so every pad's timestamps relate to UTC the same way. ## Plan -Use the current broadcast-wide segment timeline and clock contract, not the -old per-rendition timeline or removed `set_wall` method. Map the buffer PTS -through its TIME segment into the broadcast's running-time domain before -publication, preserving valid within-group B-frame reordering. - -Choose the wall epoch once. Prefer GstReferenceTimestampMeta only when it -names a recognized absolute clock domain; otherwise relate the pipeline clock, -base time, running time, and local SystemTime. Account for the mapped PTS when -computing PTS zero. An unidentified reference clock is not UTC. Every pad uses -the same mapping rather than independently sampling a new epoch. - -Keep that mapping through flushes, encoder restarts, and source PTS resets. -Translate a restarted source forward on the existing clock, including idle -gaps; refuse a source that cannot be mapped consistently. Discontinuity -markers never change wall or retime retained records. Do not add per-record -anchors or define a GStreamer-specific catalog shape. - -Test recognized reference metadata, deterministic local-clock fallback, -delayed first buffers, multiple pads, timescale conversion, numeric limits, -source restarts, idle gaps, and a system-clock adjustment. Existing timeline -records remain unchanged. Consume the prerequisite's catalog format; no new -transport TIMESTAMP/TIMESCALE semantics, synchronization protocol, or drift -correction is introduced here. Run the GStreamer CI and `interop --all` lanes. +The PTS mapping already exists: `rs/moq-gst/src/sink/pad.rs` maps each +buffer PTS through its TIME segment into the shared running-time domain +(`rs/moq-gst/src/sink/timeline.rs`), and #4480 made rewinds drop frames. Only +the wall-epoch choice remains. + +Choose the wall epoch once per broadcast. Prefer GstReferenceTimestampMeta +only when it names a recognized absolute clock domain; otherwise relate the +pipeline clock, base time, running time, and local SystemTime. An unidentified +reference clock is not UTC. Every pad uses the same epoch rather than sampling +its own. Do not define a GStreamer-specific catalog shape. + +Decided in the 2026-09-30 audit: a restart is a new broadcast epoch, not a +forward re-anchor on the old clock (per remove-live and +[GStreamer and OBS](/quest/m1/broadcast-epoch/gst-obs.md)), and +[#3115](/quest/m2/3115-moqsink-the-publication-has-no-generation-so-a-flush.md) +handles the sink side. + +Test recognized reference metadata, the deterministic local-clock fallback, +delayed first buffers, and multiple pads sharing one epoch. ## Closes - [#3021](https://github.com/moq-dev/moq/issues/3021) - close this issue when the quest finishes + +## Related + +- [GStreamer and OBS](/quest/m1/broadcast-epoch/gst-obs.md) - a restarted pipeline publishes a new epoch diff --git a/quest/m1/README.md b/quest/m1/README.md index 9d4df1e022..aac1fbc66a 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -15,15 +15,19 @@ Give one agent ownership of each shared code area at a time (origin/auth, the JS Reader, audio playback, media containers and archive, bindings, worker transport, benchmark tooling); worktrees isolate commits, not semantics. +A line with no named consumer waits in m2 until one appears. The 2026-09-30 +audit moved P2P, one-port, ladder, processor, timed metadata, the installer, +the OBS GPU paths, the IETF half of rs2ts, and the unmeasured io_uring and +QUIC studies there on that rule. + ## Required - [Cluster routing](/quest/m1/cluster-routing/README.md) - an announcement says where a broadcast originates, not how to reach it, and a relay hears only the prefixes its clients asked for -- [A/V clock](/quest/m1/av-clock.md) - the audio playhead drives Sync.reference while audio plays, through per-track sync handles - [Track tail interop](/quest/m1/track-tail-interop.md) - a Rust publisher ending a track with a group in flight is read to its end by the JS subscriber, and the reverse, in `just test interop` +- [Merge queue](/quest/m1/merge-queue.md) - the required checks run on `merge_group`, so a stale green check can no longer break main - [Binding audio delay](/quest/m1/binding-surface.md) - moq-ffi and every wrapper configure and observe audio playout delay - [FFI shape](/quest/m1/ffi-shape/README.md) - the bindings mirror Rust's layers: net at the root, then media, json, flate, audio, and video namespaces built from the handle below - [Raw stream codes](/quest/m1/raw-stream-codes.md) - raw QUIC stream resets and stops carry the application's code, not an HTTP/3-mapped one -- [Mobile ownership](/quest/m1/mobile-ownership.md) - decide whether Rust or platform code owns mobile capture, codecs, and rendering - [IETF drain before close](/quest/m1/ietf-drain-before-close.md) - moq-transport sessions deliver finished tracks before a graceful close, as moq-lite does - [Demo serve-hls renditions](/quest/m1/serve-hls-renditions.md) - `just pub serve-hls` serves 720p and 144p instead of two 256-wide copies - [moq play drain tail](/quest/m1/play-drain-tail.md) - retired renditions and finite tracks play their last 10 ms of audio @@ -36,17 +40,19 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Request stream cancel](/quest/m1/request-stream-serve.md) - a lite publisher stops resolving a SUBSCRIBE or FETCH once the requester FINs or resets, through one wrapper that owns every request stream's reader - [Session death parity](/quest/m1/session-death.md) - a local close ends tracks cleanly in both languages, and JS group readers see the session's error on session death - [Watch and publish under CSP](/quest/m1/csp-assets.md) - blob workers stay the default; strict-CSP apps host the files and set a base URL -- [More tests under load](/quest/m1/test-flakes-2.md) - the second round of load-only failures, fixed at the cause +- [More tests under load](/quest/m1/test-flakes-2/README.md) - the second round of load-only failures, one quest per flake, fixed at the cause - [CI runner stalls](/quest/m1/ci-runner-stalls.md) - the 0.4 to 0.8 s freezes of both interop tracks on CI are attributed from a week of nightlies and fixed or told apart from playback bugs - [Catalog estimate rate](/quest/m1/catalog-estimate-rate.md) - a rising `jitter`/`delay` estimate republishes the catalog at most once a second, in js/publish and moq-mux - [Legacy end overshoot](/quest/m1/legacy-end-overshoot.md) - browser playback survives a group that starts inside the previous group's estimated end -- [Merge queue](/quest/m1/merge-queue.md) - the required checks run on `merge_group`, so a stale green check can no longer break main - [Wire compatibility](/quest/m1/wire-compat.md) - a nightly run tests this checkout against the last published release for tokens, session wire, and catalog/container - [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - unflagged loop wraps move audio and video by one shift, so A/V sync holds across wraps - [TS PSI reassembly](/quest/m1/ts-psi-reassembly.md) - `import ts` reads a PAT or PMT that spans packets or follows a nonzero pointer_field instead of aborting, and one corrupted section costs a repetition and a counted `CRC_error`, not the import +- [RTMP interleaving](/quest/m1/rtmp-interleaving.md) - isolate partial messages before optimizing assembly copies - [TS stats module](/quest/m1/ts-stats-module.md) - on dev, the TS stats types move under `ts::stats` as `Snapshot` and `Stream`, with an owned `track` - [Same-hop importers](/quest/m1/hop-aligned-import.md) - importers sharing a `--hop` and fed one stream publish identical groups and timestamps, so failover survives +- [Audio capture without ALSA link](/quest/m1/capture-alsa-link.md) - moq-audio capture and playback build on Linux without linking libasound - [Capture by default](/quest/m1/capture-default.md) - moq-video and moq-audio build `capture` by default, so pre-merge checks test it and the capture gate goes away +- [Ship capture and playback](/quest/m1/cli-packaging.md) - a released moq binary can capture and play, which no distribution currently enables - [Auth client CA](/quest/m1/relay-auth-client-ca.md) - on dev, `auth::Config::validate` and `init` take the client-CA flag, so no caller can skip the check - [Data track clock](/quest/m1/data-track-clock.md) - JSON and binary data tracks stamp on the catalog's clock at write time, matching the media's anchored clock - [Go and Dart doc samples](/quest/m1/doc-samples-go-dart.md) - Go and Dart doc samples compile against their wrappers @@ -60,7 +66,6 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Tooling](/quest/m1/tooling/README.md) - justfiles become a one-line menu over `sh/`, one impact map scopes CI, and every workflow step runs a recipe - [In-band auth](/quest/m1/auth/README.md) - a session tells its peer what it may publish and subscribe to, unions tokens presented in band, and fails loud on an out-of-scope publish - [Dropped sources](/quest/m1/dropped-sources.md) - track consumers see the producer's real error on every end path, never `Dropped` -- [Shaper virtual time](/quest/m1/shaper-virtual-time.md) - `moq-shaper` tests judge seeded decisions on paused time, not on wall-clock delivery under load - [C++ through moq-ffi](/quest/m1/cpp/README.md) - generated C++ over moq-ffi with futures and expected-style errors, shipped as a tarball, vcpkg, and Conan, and adopted by the OBS plugin - [Generated C bindings](/quest/m1/c/README.md) - C generated from moq-ffi ships as `moq-c` 0.8.0 and replaces the hand-written libmoq - [OBS native codecs](/quest/m1/obs-moq-video/README.md) - replace FFmpeg video and audio decoding with moq-video and moq-audio, deliver GPU frames, and use native audio/video encoders @@ -72,6 +77,7 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [JS rendition ranking](/quest/m1/js-ranked.md) - `@moq/hang` ranks video renditions like Rust, and `@moq/watch`'s fallback uses it - [Audio rendition pick](/quest/m1/audio-ranked.md) - single-track FLV/RTMP and WHEP serve the best audio rendition, not the first by name - [FLV catalog stream](/quest/m1/flv-catalog-stream.md) - on dev, `flv::Export` takes a catalog stream like fmp4, replacing `with_select` +- [io_uring flow control](/quest/m1/uring-flow-control-windows.md) - the relay's io_uring workers honor the QUIC flow-control windows instead of refusing them - [Own the QUIC stack](/quest/m1/quic/README.md) - the moq-noq fork carries ACK progress, reliable reset, hierarchical scheduling, deadlines, peer limits, and qmux - [QoS](/quest/m1/qos/README.md) - broadcast health: relay starvation and timeliness histograms, on dev - [Catalog track alias](/quest/m1/catalog-track-alias.md) - catalog rendition keys become aliases with an optional `track` name, so one catalog lists renditions from several broadcasts @@ -93,7 +99,6 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Generated @moq/net](/quest/m1/rs2ts/README.md) - the browser runs moq-net as TypeScript generated from the Rust source, retiring js/net's hand-written protocol and model code - [Plan: watch worker](/quest/m1/plan-watch-worker.md) - prototype an invisible page worker against app-spawned workers, and land the jank harness that decides - [Watch worker](/quest/m1/watch-worker.md) - watch playback runs in a worker onto an OffscreenCanvas, so main-thread jank never stalls video or audio -- [RTMP interleaving](/quest/m1/rtmp-interleaving.md) - isolate partial messages before optimizing assembly copies - [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - with the default pool, relay memory plateaus at the expiry window on every version - [Plan: cache age-out](/quest/m1/cache-wall-eviction.md) - a swept benchmark decides whether the track cache ages groups out on wall time without a write - [Frame slot charge](/quest/m1/frame-slot-charge.md) - a group's frame slots past the first four count against the cache pool, including capacity a released group keeps @@ -108,14 +113,15 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [#933](/quest/m1/933-video-rotation-metadata-not-propagated-from-mobile-camera.md) - the catalog rotation follows the live camera's orientation - [#2848](/quest/m1/2848-follow-the-bandwidth-grant-in-moq-audio-instead-of.md) - the Opus producer follows its bandwidth grant through the settled `moq_mux::rate::Control` - [Time stretch](/quest/m1/watch-audio-time-stretch.md) - js/watch: the audio ring converges by time-stretching instead of skipping or going silent +- [A/V clock](/quest/m1/av-clock.md) - the audio playhead drives Sync.reference while audio plays, through per-track sync handles - [Native audio quality](/quest/m1/audio-quality-native.md) - the browser lane's profiles, budgets, and metric schema run against `moq play` on a dummy device - [Caption import](/quest/m1/captions-import.md) - fMP4 and MKV subtitle tracks import as text renditions instead of erroring or being dropped - [MSF caption roles](/quest/m1/captions-msf.md) - an MSF caption, subtitle, or sign-language track survives conversion to a hang catalog -- [Colour model](/quest/m1/color-model.md) - the catalog describes a rendition's colour and HDR properties instead of leaving a TODO +- [Encoder colour](/quest/m1/color-model.md) - every moq-video encode path signals the colour its output actually has, or refuses instead of mislabelling - [Open-GOP leading pictures](/quest/m1/open-gop-leading-pictures.md) - a viewer joining at a recovery point drops the leading pictures it cannot decode; continuous viewers keep them - [Catalog warmup](/quest/m1/catalog-warmup.md) - `warmup` on video and audio renditions, in the catalog and the draft - [Audio warmup](/quest/m1/audio-warmup.md) - a viewer joining an Opus rendition mid-stream never hears the unconverged first 80 ms -- [#3021](/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md) - GStreamer maps every pad onto one continuous broadcast clock across source restarts +- [#3021](/quest/m1/3021-moq-gst-anchor-generated-media-timelines-to-wall-clock.md) - moq-gst picks the broadcast wall epoch; a restarted source is a new epoch, not a forward re-anchor - [TS byte schedule](/quest/m1/ts-export-byte-schedule.md) - moq export ts places PCRs and padding on the byte grid `mpegts.muxRate` implies, so a receiver can clock off arrival - [Release profile](/quest/m1/release-profile.md) - every release build gets fat LTO, one codegen unit, and stripping from the workspace profile instead of three script exports - [Size report](/quest/m1/size-report.md) - a nightly job reports every shipped artifact's size, native and JS, and alerts when one grows @@ -125,13 +131,11 @@ transport, benchmark tooling); worktrees isolate commits, not semantics. - [Bindings size profile](/quest/m1/ffi-size-profile.md) - a benchmark decides whether the moq-ffi builds ship at opt-level "s", which halves the dylib - [Go mirror delivery](/quest/m1/go-mirror-delivery.md) - the Go binding's staticlibs stop growing git history by ~210 MiB per release - [Relay iroh opt-in](/quest/m1/relay-iroh-opt-in.md) - moq-relay drops iroh from its defaults and shipped builds, while moq-cli keeps it for P2P +- [Rust owns mobile codecs](/quest/m1/mobile-ownership.md) - the settled verdict (Rust codecs, CVPixelBuffer bridge) is written where binding work reads it - [Dart on iOS](/quest/m1/dart-ios.md) - prove the shipped iOS native asset actually loads on a device, which no CI can - [Kotlin JVM exit](/quest/m1/kt-jvm-exit.md) - a Kotlin/JVM program exits cleanly whatever the moq-ffi runtime thread is doing, like Python does since #3766 - [Dart publish](/quest/m1/dart-publish.md) - the packages are built and dry-run clean but exist nowhere consumers can install from - [Dart codec parity](/quest/m1/dart-codecs.md) - Dart is the one binding that cannot originate media -- [#2850](/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md) - js/net: decode messages synchronously from buffered bytes and delete the publisher read-ahead queue +- [#2850](/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md) - js/net caps the publisher's subscription controls so a flood cannot grow memory without bound - [`moq --listen` admission](/quest/m1/cli-serve.md) - a listening CLI session is authenticated, scoped, counted, and drained like a relay's instead of accepting everything - [#709](/quest/m1/709-automatic-letsencrypt-support.md) - the relay provisions and renews its own ACME certificate through rustls-acme over TLS-ALPN-01, persisted on disk -- [Audio capture without ALSA link](/quest/m1/capture-alsa-link.md) - moq-audio capture and playback build on Linux without linking libasound -- [Ship capture and playback](/quest/m1/cli-packaging.md) - a released moq binary can capture and play, which no distribution currently enables -- [io_uring flow control](/quest/m1/uring-flow-control-windows.md) - the relay's io_uring workers honor the QUIC flow-control windows instead of refusing them diff --git a/quest/m1/archive/enrollment-flake.md b/quest/m1/archive/enrollment-flake.md index 4395024703..f8a02346e0 100644 --- a/quest/m1/archive/enrollment-flake.md +++ b/quest/m1/archive/enrollment-flake.md @@ -22,5 +22,5 @@ Public API: none. Wire: none. ## Related -- [More tests hold up under load](/quest/m1/test-flakes-2.md) - the same +- [More tests hold up under load](/quest/m1/test-flakes-2/README.md) - the same round of load-only failures on `main` diff --git a/quest/m1/archive/writer.md b/quest/m1/archive/writer.md index 16eb74108d..3a1329e1f7 100644 --- a/quest/m1/archive/writer.md +++ b/quest/m1/archive/writer.md @@ -70,6 +70,14 @@ Wait the deletion grace period from successful recovery, then delete unreference left by interrupted expiration or uploads. Failed or incomplete recovery must prevent deletion. Preserve `.info` and timeline checkpoint objects. +On resume, fail loud on a track that goes backward. The resume step already +refuses a group ID at or below the recovered track's last one; also refuse a +first group whose timestamp is before the recovered track's last recorded +timestamp, instead of writing overlapping media time. The caller starts a new +prefix. Test a backward and a forward restart. Decided in the 2026-09-30 +audit: folded in from a separate quest, and the archive format may break in +place since it is unreleased. + Keep archive policy out of protocol libraries. As the native application that owns its storage and track choices, `moq-cli` attaches the writer to every import path and enrolls the resulting `broadcast::Consumer` tracks. Downstream diff --git a/quest/m1/auth/README.md b/quest/m1/auth/README.md index 78ddbe011a..df7c411cab 100644 --- a/quest/m1/auth/README.md +++ b/quest/m1/auth/README.md @@ -47,8 +47,8 @@ Decisions settled while planning, recorded so review does not relitigate them: root, and every token in a union shares the connection's root. Unscoped permission is `**`; an empty union grants nothing. Legacy AUTH wire codecs explicitly convert representable prefix unions, where `[""]` means all, - and refuse patterns they cannot represent. [Pattern interest](/quest/m1/path-patterns.md) - upgrades AUTH and ANNOUNCE_REQUEST wire fields together without changing + and refuse patterns they cannot represent. Pattern interest (#4277, on this + line) upgrades AUTH and ANNOUNCE_REQUEST wire fields together without changing the public pattern-valued grant type. - **Fail loud by aborting the session.** A publisher whose origin announces a broadcast outside the union aborts the session with `Unauthorized`, naming @@ -111,7 +111,7 @@ existing lite-06 ALPN. - [Expired token error](/quest/m1/auth/expired-error.md) - an expired token reports `Error::Expired`, not `Unauthorized`, in Rust, JS, and the bindings - [Bindings](/quest/m1/auth/bindings.md) - grants and tokens reach every - binding through moq-ffi and libmoq + binding through moq-ffi - [Token in band](/quest/m1/auth/token-in-band.md) - the credential can leave the URL: a session starts on what the URL carried and its AUTH streams add the rest, with the URL kept for peers below lite-06 @@ -122,3 +122,4 @@ existing lite-06 ALPN. - [Expiring media grants](/quest/m2/processor/grant-lease.md) - a worker's lease renewal is a new in-band token +- [P2P](/quest/m2/p2p/README.md) - the first consumer of hop-bound peer grants diff --git a/quest/m1/auth/bindings.md b/quest/m1/auth/bindings.md index 6bd1a36ab2..c46c2540c8 100644 --- a/quest/m1/auth/bindings.md +++ b/quest/m1/auth/bindings.md @@ -2,43 +2,44 @@ ## Goal -Every binding can read a session's grant and present more tokens: moq-ffi and -libmoq expose it, and the hand-written Python, Swift, Kotlin, Go, and Dart -wrappers and their docs carry it. An OBS user whose token is about to expire -mid-stream can be handed a new one without the plugin reconnecting. +Every binding can read a session's grant and present more tokens: moq-ffi +exposes it, the generated C and C++ bindings pick it up from moq-ffi, and the +hand-written Python, Swift, Kotlin, Go, and Dart wrappers and their docs carry +it. An OBS user whose token is about to expire mid-stream can be handed a new +one without the plugin reconnecting. ## Plan - `MoqSession` in `rs/moq-ffi/src/session.rs` gains `auth()` returning an `Arc` object, the shape `publisher()` uses, with `grant() -> - Option` (a record: publish prefixes, subscribe prefixes, expiry as - a duration) for the union, `async grant_changed() -> MoqGrant` so a caller + Option` (a record: publish patterns, subscribe patterns, expiry + as a duration, matching `auth::Grant`) for the union, `async grant_changed() -> MoqGrant` so a caller can await the next change without polling, and `async add(token: String) -> Arc` whose object carries that token's own `grant()` and `closed()` and whose release withdraws it, raising the structured error moq-ffi already maps on refusal. `MoqClient` sessions reached through the reconnecting connection use the accessor [Relay tokens](/quest/m1/auth/relay-refresh.md) adds. -- libmoq: `moq_session_auth_grant`, `moq_session_auth_add`, and - `moq_auth_token_close` with the terminal-status callback contract the other - async calls use, in `rs/libmoq/src/api.rs` and the session table; - regenerate `moq.h`, and update `cpp/obs/src` only if the plugin surfaces a - token field, otherwise leave it. -- Interop: the Python, Go, and C interop clients print their grant and join +- No new libmoq API: the hand-written C ABI gets no more feature work. The + generated C and C++ bindings get this from moq-ffi, and OBS reaches it + through [C++ through moq-ffi](/quest/m1/cpp/README.md); update `cpp/obs/src` + only if the plugin surfaces a token field. +- Interop: the Python, Go, and C++ interop clients print their grant and join the assertion [Interop grants](/quest/m1/auth/interop.md) adds for Rust and JS. - Wrappers: `py/moq-rs/moq/session.py`, `swift/Sources/Moq`, `kt/.../Flows.kt` (a `Flow` over `grant_changed`), `go/wrapper/moq/session.go` (context-cancellable like the rest), and `dart/moq/lib/moq.dart`. Kotlin typealiases pick up the raw methods for free; the flow is the idiomatic add. -- Docs: `doc/lib/{py,swift,kt,go,dart,c}` each gain a short auth section, and +- Docs: `doc/lib/{py,swift,kt,go,dart}` each gain a short auth section, and `doc/lib/rs` documents `Session::auth()`. - Tests: each wrapper's existing session test reads a grant from a local relay, adds a second token minted by `moq auth`, sees the union grow, and releases it; the refusal path surfaces the structured error in each language. -Additive. +Additive. Decided in the 2026-09-30 audit: libmoq is frozen, so the C +surface comes from moq-ffi rather than new `moq_session_auth_*` calls. ## Required diff --git a/quest/m1/auth/request-token.md b/quest/m1/auth/request-token.md index 7f501b10d0..f733630897 100644 --- a/quest/m1/auth/request-token.md +++ b/quest/m1/auth/request-token.md @@ -75,3 +75,7 @@ to) and on `moq_auth::Client` (the per-request lease). Wire: none new; the param - [Relay tokens](/quest/m1/auth/relay-refresh.md) - supplies the lease revalidation the per-request lease reuses + +## Related + +- [Legal IETF input](/quest/m0/ietf-legal-input.md) - also edits `decode_params!`; refresh this Plan when it lands, since the strict decoder then decodes and ignores the token diff --git a/quest/m1/auth/token-in-band.md b/quest/m1/auth/token-in-band.md index 59bf43eb58..574bf9c96c 100644 --- a/quest/m1/auth/token-in-band.md +++ b/quest/m1/auth/token-in-band.md @@ -24,8 +24,11 @@ AUTH can carry the full grant once the pattern-interest prerequisite lands. the client makes. A `?jwt=` in the URL stays a member of the union, which is how cluster dial targets keep their per-peer credential, and per-dial extras use the session's `auth().add()` once connected. moq-ffi - `MoqClient::set_tokens`, libmoq `moq_client_set_tokens` (mirrored in the - wrappers and `cpp/obs/src`), and `js/net`'s `connect` options field follow. + `MoqClient::set_tokens` (mirrored in the wrappers, and reaching the + generated C and C++ bindings and OBS through + [C++ through moq-ffi](/quest/m1/cpp/README.md)) and `js/net`'s `connect` + options field follow. Decided in the 2026-09-30 audit: no new libmoq API, + since it gets no more feature work. - Credential refresh belongs with token presentation. Resolve the configured credential source before each dial. After an authorization refusal, resolve it once more and retry only when the credential changed; do not loop on a diff --git a/quest/m1/av-clock.md b/quest/m1/av-clock.md index c82e3ff855..fe19a25091 100644 --- a/quest/m1/av-clock.md +++ b/quest/m1/av-clock.md @@ -11,6 +11,9 @@ the next re-anchor. ## Plan +Moved from m0 to m1 in the 2026-09-30 audit: it waits on the whole jitter +line and is a published `@moq/watch` break on dev, so it is not in flight. + Settled: per-track handles, and this quest lands them. `sync.track("audio")` and `sync.track("video")` each report their advertised delay and measured spread, and one is nominated as the clock source. `SyncInput` diff --git a/quest/m1/bbr-ack-cleanup.md b/quest/m1/bbr-ack-cleanup.md index 082c39ae3b..bb0db79437 100644 --- a/quest/m1/bbr-ack-cleanup.md +++ b/quest/m1/bbr-ack-cleanup.md @@ -10,10 +10,10 @@ behavior. ## Plan -The fix lives in moq-dev/noq. In released 1.3.1 (`ff9d2ab5`), -[`on_end_acks`](https://github.com/moq-dev/noq/blob/ff9d2ab518cfb155f9ebb9925f1c784665eac92a/noq-proto/src/congestion/bbr3/mod.rs#L1783) -runs `retain` over all tracked packets, then scans them again to mark stale -entries. Draining a flight with fixed-size ACK batches has quadratic total +The fix lives in moq-dev/noq. The baseline is the current releases: the +workspace pins moq-noq 1.3.2 and 2.0.0 is out. In both, `on_end_acks` +(`noq-proto/src/congestion/bbr3/mod.rs:1845`) runs `retain` over all tracked +packets, then scans them again to mark stale entries. Draining a flight with fixed-size ACK batches has quadratic total cleanup work. [Google QUICHE](https://github.com/google/quiche/blob/c961965aa3ee8f2b6f05ebcac794f7854101adcd/quiche/quic/core/congestion_control/bandwidth_sampler.cc#L377) uses packet-number lookup and obsolete-prefix reclamation; use that as a reference without copying a TCP or single-space assumption into QUIC. @@ -57,3 +57,4 @@ is needed. - [BBR starvation edges](/quest/m1/quic/bbr-app-limited-edges.md) - also edits `bbr3/mod.rs`; one owner there at a time - [Benchmark comparisons](/quest/m1/performance-comparisons.md) - reusable measurement guidance, not a prerequisite for this fix +- [Loss sampling](/quest/m2/quic-bbr-loss-parity.md) - preserve packet metadata needed by the separate loss-sample repair; both edit `bbr3/mod.rs`, so sequence them diff --git a/quest/m1/bench-ci.md b/quest/m1/bench-ci.md index cc3d7c8bbd..7376f8e4a5 100644 --- a/quest/m1/bench-ci.md +++ b/quest/m1/bench-ci.md @@ -50,3 +50,4 @@ a GitHub App: ## Related - [Benchmark comparisons](/quest/m1/performance-comparisons.md) - extends the same `bench/run.sh` with repeated paired rounds +- [Bench coverage](/quest/m2/bench-coverage.md) - more targets for this job to track diff --git a/quest/m1/broadcast-epoch/README.md b/quest/m1/broadcast-epoch/README.md index 337297edc3..96a0172d3e 100644 --- a/quest/m1/broadcast-epoch/README.md +++ b/quest/m1/broadcast-epoch/README.md @@ -15,8 +15,8 @@ wire message changes. At an epoch-aware relay, a request for a bare name resolves to its newest live epoch on every protocol version. Non-goals: redundant publishers sharing one epoch, and failing over between -them faster than the keep-alive (the [redundant ingest](/quest/m2/redundant-ingest.md) -study). Also out of scope: trusting the publisher's clock (a far-future epoch +them faster than the keep-alive (a question +[Cluster routing](/quest/m1/cluster-routing/README.md) owns). Also out of scope: trusting the publisher's clock (a far-future epoch wins until its route goes away). ## Plan @@ -61,5 +61,5 @@ This README owns: - [Origin](/quest/m1/broadcast-epoch/origin.md) - moq-net publish mints an epoch, consumers follow the newest live one, and bare requests resolve to it on every version - [Apps](/quest/m1/broadcast-epoch/apps.md) - moq-cli, the browser publish and watch components, and demo/web publish under epochs and play bare names - [Gateways](/quest/m1/broadcast-epoch/gateways.md) - RTMP, SRT, and WHIP ingest mint an epoch per incoming connection, so an encoder reconnect is a clean takeover -- [Bindings](/quest/m1/broadcast-epoch/bindings.md) - moq-ffi, libmoq, and every wrapper expose the epoch and inherit the default +- [Bindings](/quest/m1/broadcast-epoch/bindings.md) - moq-ffi and every wrapper expose the epoch and inherit the default - [GStreamer and OBS](/quest/m1/broadcast-epoch/gst-obs.md) - moqsink and the OBS plugin publish each run under a fresh epoch diff --git a/quest/m1/broadcast-epoch/bindings.md b/quest/m1/broadcast-epoch/bindings.md index ed5567ef69..e7bb2876fd 100644 --- a/quest/m1/broadcast-epoch/bindings.md +++ b/quest/m1/broadcast-epoch/bindings.md @@ -2,8 +2,9 @@ ## Goal -`moq-ffi`, `libmoq`, and the py, swift, kt, go, and dart wrappers publish under -the default epoch and follow bare names like Rust. The epoch of a published or +`moq-ffi` and the py, swift, kt, go, and dart wrappers publish under the +default epoch and follow bare names like Rust. The generated C and C++ +bindings pick it up from moq-ffi; libmoq gets no new API. The epoch of a published or consumed broadcast is readable, and a caller can pass an explicit one. The reconnect counter `session.epoch()` is renamed so "epoch" has one meaning. @@ -13,9 +14,12 @@ reconnect counter `session.epoch()` is renamed so "epoch" has one meaning. argument. Keep the surface to what a binding consumer needs. - Rename `session.epoch()` in every binding (for example to `connects()`). That is a break, so it lands on `dev`. -- Update `doc/lib/{py,swift,kt,go,dart,c}` per the cross-package sync table, +- Update `doc/lib/{py,swift,kt,go,dart}` per the cross-package sync table, and run `just test smoke --all`. +Decided in the 2026-09-30 audit: libmoq is frozen (renamed `rs/moq-c` on +`dev`), so C and C++ consumers get epochs from moq-ffi. + ## Required - [Origin](/quest/m1/broadcast-epoch/origin.md) - the behavior the bindings surface diff --git a/quest/m1/broadcast-epoch/gst-obs.md b/quest/m1/broadcast-epoch/gst-obs.md index 5ae7824177..35604ee5e4 100644 --- a/quest/m1/broadcast-epoch/gst-obs.md +++ b/quest/m1/broadcast-epoch/gst-obs.md @@ -10,9 +10,11 @@ restarted pipeline or a stop and start in OBS is a clean takeover for viewers. `moqsink` takes the origin default per session. When [#3115](/quest/m2/3115-moqsink-the-publication-has-no-generation-so-a-flush.md) lands, each of its publication generations is a new epoch. OBS gets it -through libmoq. Update `doc/bin/gstreamer.md` and `doc/bin/obs.md` if they -show paths. +through the generated C++ bindings from moq-ffi (decided in the 2026-09-30 +audit: libmoq gets no new API). Update `doc/bin/gstreamer.md` and +`doc/bin/obs.md` if they show paths. ## Required -- [Bindings](/quest/m1/broadcast-epoch/bindings.md) - OBS publishes through libmoq +- [Bindings](/quest/m1/broadcast-epoch/bindings.md) - moq-ffi exposes epochs +- [C++ through moq-ffi](/quest/m1/cpp/README.md) - OBS publishes through the generated C++ diff --git a/quest/m1/broadcast-epoch/origin.md b/quest/m1/broadcast-epoch/origin.md index 1e065f18ac..0c02eab0ba 100644 --- a/quest/m1/broadcast-epoch/origin.md +++ b/quest/m1/broadcast-epoch/origin.md @@ -32,6 +32,9 @@ any wire change. exclusion and the per-path fronts from #3312 stay intact. - Benchmark resolution swept over epochs per name and names per origin, so following does not scan the table. +- Open: does a catalog `broadcast` reference by bare name pin the epoch its + catalog came from, or follow the newest? Settle it with + [Catalog track alias](/quest/m1/catalog-track-alias.md). - Update `doc/concept` and `drafts/draft-lcurley-moq-lite.md` wherever they describe resolution or takeover. The rule is a relay behavior, so state it in the draft even though no field changes. @@ -43,4 +46,6 @@ Wire: none. ## Related +- [Catalog track alias](/quest/m1/catalog-track-alias.md) - cross-broadcast catalog references, which must pick an epoch + - [#2991](/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md) - a new epoch starts each track at sequence 0 diff --git a/quest/m1/cache-wall-eviction.md b/quest/m1/cache-wall-eviction.md index f00fe1a37c..7643c0075e 100644 --- a/quest/m1/cache-wall-eviction.md +++ b/quest/m1/cache-wall-eviction.md @@ -42,3 +42,4 @@ Public API: none from the plan. Wire: none. ## Related - [Cache expiry growth](/quest/m1/cache-expiry-growth.md) - relay memory past the expiry window, in the same cache +- [Cache shard](/quest/m2/cache-shard.md) - the pool's shared counters under many workers diff --git a/quest/m1/cli-packaging.md b/quest/m1/cli-packaging.md index 730b83b3bc..33a48b9b30 100644 --- a/quest/m1/cli-packaging.md +++ b/quest/m1/cli-packaging.md @@ -30,6 +30,20 @@ than at run. Then verify the shipped artifact runs `moq devices` and `moq play` on each platform, since a feature that compiles into the binary and then fails to open a device is the same gap one layer down. +Also enable `v4l2` in the Linux ARM release build, so a released binary on a +Raspberry Pi 4 publishes from `moq import capture` through the V4L2 M2M +hardware encoder (`rs/moq-video/src/v4l2.rs`, already run on a Pi 4's +`bcm2835-codec`) with no GStreamer detour. Verify that once on a Pi 4. + +Add a board hardware note to `doc/bin/cli.md` next to the capture build +instructions: Raspberry Pi 5 has no video encoder and Jetson Orin Nano ships +without NVENC; Pi 4, CM4, Zero 2 W, and Orin NX and above encode. RK3588 +encodes through rkmpp in a vendor kernel, not V4L2, so it stays on the +`moq-gst` route. + +Decided in the 2026-09-30 audit: the v4l2 encode quest folded in here, since +its remaining work was one release feature flag and a doc note. + ## Required - [Audio capture without runtime system libraries](/quest/m1/capture-alsa-link.md) - the microphone path must start without system audio libraries before every distribution can ship it diff --git a/quest/m1/cli-serve.md b/quest/m1/cli-serve.md index 613a54cff5..298cd2a3f6 100644 --- a/quest/m1/cli-serve.md +++ b/quest/m1/cli-serve.md @@ -29,3 +29,12 @@ relay. `moq import --listen`; an unscoped one is refused when a key is configured; the unauthenticated import-to-export smoke keeps passing with `--auth-public`. + +Decided in the 2026-09-30 audit: no longer waits on +[`moq relay`](/quest/m2/moq-relay-subcommand.md). `moq-cli` already depends +on `moq-relay`, and the open-relay listener is a security gap that should not +wait on an m2 subcommand. + +## Related + +- [`moq relay`](/quest/m2/moq-relay-subcommand.md) - the CLI later hosts the whole relay diff --git a/quest/m1/cluster-routing/README.md b/quest/m1/cluster-routing/README.md index 0f3d74f78f..821bd5f080 100644 --- a/quest/m1/cluster-routing/README.md +++ b/quest/m1/cluster-routing/README.md @@ -5,15 +5,20 @@ A broadcast event reaches each relay at most once, and a relay learns only the prefixes its own clients asked for. An announcement says a path exists at an origin relay, at a cost; how to reach that origin comes from a shared relay -topology, so no announcement inside a cluster carries a hop list. This quest -records the design; its wire and implementation quests are planned from the -simulator's report. +topology, so no announcement inside a cluster carries a hop list. A relay's +memory scales with what it serves, not with what the mesh knows. This +questline records the design; its wire and implementation quests are planned +from the simulator's report. Non-goals: warm re-origination (a warm relay would be one more origin with a cost, so leave room for it), and a permanently mixed-version cluster. ## Plan +This is a questline with no children yet. Its children (wire and +implementation) are planned next in a separate `/quest-plan` session, from +the decisions and open questions below. + ### Why not path vector or Babel Today every relay advertises its best route to every peer not already in the @@ -167,6 +172,29 @@ registry. What decides the wire: the same scenario costs 18.6M messages instead of 189k. Per-origin seqnos, above, end both. +### Memory before and after + +On-demand announcements bound the route table by demand; measure that saving +before and after the implementation lands. Keep two costs apart: the route +table and per-announcement state (`RouteEntry` plus `ServeState`) scale with +announcements and routes, while the served-content cache a `ServeState` +materializes scales with demand. + +Every published figure is stale. The old baseline was 8.8 KB per announced +broadcast plus 4.3 KB per extra route on `adad52b`, measured with two +throwaway `moq-net` examples driving an origin under a counting allocator and +reading `/proc/self/statm`. Since then +[moq#2989](https://github.com/moq-dev/moq/pull/2989) cut `kio`'s inline waiter +slots from 32 to 4 (a `kio::State<()>` went from 896 B to about 200 B, and +`kio`'s `tests/waiter_allocs.rs` pins that lever as spent), and +[moq#3225](https://github.com/moq-dev/moq/pull/3225) made a standby route a +table entry rather than an object graph. Neither example is committed, since +they need `#[doc(hidden)]` size probes on private types. Rebuild them and +restate the per-broadcast and per-route cost, the per-peer session +bookkeeping (`announce_ids`, `held`, `watched`), and the shed threshold on a +degree-5, 1 GB node. Chat-shaped traffic (one broadcast per channel or per +chatter) depends on the answer. + ### Open questions - Sharding registries by HRW over a prefix key once one registry cannot hold @@ -194,6 +222,14 @@ registry. What decides the wire: `doc/concept/use-case/contribution.md`). Inside a cluster no announcement carries a hop list, so two encoders on different ingest relays become two origins. Keep the documented behavior or change the docs in the same PR. + Say whether same-hop semantics survive, or + [Same-hop importers](/quest/m1/hop-aligned-import.md)' work is thrown + away. The redundant ingest study folded in here: whether the pair claims one + `@` epoch, what enforces the aligned groups and matching catalog the + docs only ask for, and who declares the incumbent dead early (a failover + service that retracts it, or active-active delivery to the relay). Weigh it + against the moq-transport rule that each publisher of a namespace must be + asked (#3697). The answer may be a no-go. - Whether equal-cost next hops should spread by a hash of the path. A fixed tie-break sends every path through the same neighbour and its failure takes them all. @@ -204,4 +240,8 @@ registry. What decides the wire: ## Related +- [Same-hop importers](/quest/m1/hop-aligned-import.md) - builds on `--hop` failover, which this line must keep or replace +- [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - a redundant pair would share one epoch + - [Cross-relay delivery under bursts](/quest/m1/cross-relay-bursts.md) - its #4349 report also shows closed broadcasts announced for up to 229 s and flapping between Retracted and Announced across nodes, evidence for per-incarnation seqnos +- [Routing cost domains](/quest/m3/routing-cost-domains.md) - cost across the cluster boundaries this keeps path vector diff --git a/quest/m1/color-model.md b/quest/m1/color-model.md index ab20fc07fd..45fed149ab 100644 --- a/quest/m1/color-model.md +++ b/quest/m1/color-model.md @@ -1,11 +1,11 @@ -# [M] Catalog colour model +# [M] Encoders signal the colour they produce ## Goal -The Hang catalog describes a video rendition's colour: primaries, transfer -characteristics, matrix coefficients, range, and the HDR10 mastering display and -content light levels. A renderer can set up its pipeline from the catalog -instead of guessing or reparsing the bitstream. +Every moq-video encode path signals the colour its output pixels actually +have. The CUDA, D3D, and Android paths preserve primaries, transfer, matrix, +and range through conversion, and a path that cannot honour the requested +colour converts correctly or refuses instead of mislabelling. ## Plan @@ -15,40 +15,13 @@ warn about a known color mismatch and then label unchanged pixels as the requested color: convert correctly or refuse. Preserve color information through CUDA, D3D, and Android paths and test the signaled VUI against pixels. -`rs/hang/src/catalog/video/mod.rs` has carried a bare `// TODO color space` since -the config was written. Two codecs already expose colour per their own syntax: -`VP9` carries primaries, transfer characteristics, matrix coefficients, and -range inside its codec string, and `AV1` has the same fields, which -`rs/moq-mux/src/codec/av1/import.rs` fills from the sequence header. They only -round-trip through muxers (the fMP4 exporter writes VP9's into `vpcC`); no -renderer consumes either, H.264 and H.265 have no equivalent, and there is no HDR10, -mastering display, or content light handling in `rs/` or `js/` at all. So an -HDR broadcast is delivered today and rendered as if it were SDR, whatever the -source signalled. +Decided in the 2026-09-30 audit: split from the catalog colour model, which +moved to [Catalog colour](/quest/m2/color-catalog.md) because no renderer +consumes colour from the catalog today. Encoder correctness stays in m1 +because a mislabelled stream is wrong for every consumer. -Model the codec-neutral properties rather than one codec's syntax, so H.264 and -H.265 VUI, AV1 colour config, and a container's `colr` box all populate the same -fields. Generalize the existing VP9 and AV1 fields into that model rather than -adding a second colour source beside them: the codec-specific fields keep their -wire meaning (the VP9 codec string is defined by its spec) and feed the neutral -fields at import, so a renderer reads one place. Fill them at import from -whichever source the rendition provides, and emit them on export. +## Related -Settle the precedence before adding the fields, because these sources disagree -in real files and a renderer must not see a different answer per import path. -Prefer the bitstream over the container: VUI and the AV1 colour config are what -the encoder actually signalled and travel with the elementary stream, while a -`colr` box is added by a muxer that may be wrong or stale. Record which source -won so a conflict is diagnosable rather than invisible, and treat an -unspecified value as absent rather than as a signalled default, so a partial -container box does not override a complete bitstream. - -Static display properties belong here rather than in a timed track, which is -also where the HDR10 metadata that lives in H.26x SEI should land once it can be -read. That is the one seam with the SEI line, and it runs in one direction: -this quest gives display metadata a home, and does not depend on SEI work. - -Test each source of truth in isolation, a source that signals nothing, a -conflict between VUI and container resolving to the bitstream, a container box -that fills a gap the bitstream left unspecified, and an SDR round trip that -stays byte-identical. +- [Catalog colour](/quest/m2/color-catalog.md) - the catalog describes a rendition's colour and HDR properties +- [SEI sidecars](/quest/m2/sei.md) - moves SEI out of the video track; + the display metadata inside it needs the home this quest builds diff --git a/quest/m1/cpp/README.md b/quest/m1/cpp/README.md index e46e976821..7fedd405dd 100644 --- a/quest/m1/cpp/README.md +++ b/quest/m1/cpp/README.md @@ -2,7 +2,7 @@ ## Goal -A C++ developer adds one registry line or one tarball, includes ``, +A C++ developer adds one release tarball, includes ``, and holds the whole moq-ffi surface (session, origin, broadcast, track, group, media, audio and video) as RAII objects whose async operations return cancellable futures, with no `user_data` plumbing, no handle integers, and no @@ -41,11 +41,12 @@ One library, C++17 floor (OBS's baseline), feature-gated extras: `co_await` on a future under `__cpp_impl_coroutine`, `std::expected` under `__cpp_lib_expected`. Never a second library per standard. -Distribution is all of: a release tarball with a CMake package config and -pkg-config file (mirroring `libmoq.yml`), a vcpkg registry we own, and a Conan -remote we own, the latter two fetching the prebuilt tarball so consumers never -need a Rust toolchain or the bindgen fork. vcpkg lands first; the Conan recipe -reads the same release manifest so a release bumps both. +Distribution is a release tarball with a CMake package config and pkg-config +file (mirroring `libmoq.yml`), so consumers never need a Rust toolchain or the +bindgen fork. Decided in the 2026-09-30 audit: this line promises the tarball +only. A [vcpkg registry](/quest/m2/cpp-vcpkg.md) (m2) and a +[Conan remote](/quest/m3/cpp-conan.md) (m3) fetch the same tarball later and +stay deferred. Confirmed in [#4100](https://github.com/moq-dev/moq/pull/4100): @@ -75,3 +76,6 @@ Confirmed in [#4100](https://github.com/moq-dev/moq/pull/4100): ## Related - [vcpkg registry](/quest/m2/cpp-vcpkg.md) - a registry we own serves the prebuilt package to `vcpkg` manifests +- [Conan remote](/quest/m3/cpp-conan.md) - a remote we own serves the prebuilt package to Conan +- [C# through moq-ffi](/quest/m3/cs/README.md) - the same recipe with NordSecurity's C# generator +- [Unreal prototype](/quest/m3/unreal.md) - a UE5 module consumes the package with exceptions disabled diff --git a/quest/m1/cross-relay-bursts.md b/quest/m1/cross-relay-bursts.md index 14e636a3f9..a7a31241d4 100644 --- a/quest/m1/cross-relay-bursts.md +++ b/quest/m1/cross-relay-bursts.md @@ -24,3 +24,7 @@ cluster-routing evidence, not this quest's scope. ## Closes - [#4349](https://github.com/moq-dev/moq/issues/4349) - close this issue when the quest finishes + +## Related + +- [Cluster routing](/quest/m1/cluster-routing/README.md) - owns the stale and flapping announcements from the same report diff --git a/quest/m1/dart-codecs.md b/quest/m1/dart-codecs.md index a0563a2791..b302fdee48 100644 --- a/quest/m1/dart-codecs.md +++ b/quest/m1/dart-codecs.md @@ -32,3 +32,10 @@ This quest owns Dart integration of the already-landed `MoqVideoConsumer`: expose it through the Dart wrapper, ship codec-enabled artifacts, and test subscribed video decoding from Dart in CI. Update the capability docs in the same change. Do not finish with codecs enabled but the consumer unexposed. + +Decided in the 2026-09-30 audit: wait for the codec namespaces, so the Dart +wrapper exposes encoders and decoders in their final shape once. + +## Required + +- [Codec](/quest/m1/ffi-shape/codec.md) - audio and video codecs get their own namespaces in moq-ffi diff --git a/quest/m1/dart-publish.md b/quest/m1/dart-publish.md index f51a9d4f83..f634f08214 100644 --- a/quest/m1/dart-publish.md +++ b/quest/m1/dart-publish.md @@ -39,11 +39,15 @@ hand-edited before tagging. Verify the published `moq_ffi` resolves its native asset from a clean machine with no monorepo checkout, since that download path is the one CI never exercises. -The blocker below is about not publishing a claim we cannot support: the +The iOS blocker is about not publishing a claim we cannot support: the first release is the one that reaches strangers, and pub.dev packages generally cannot be unpublished or deleted. A version may be retracted within seven days, but retraction does not erase it. +Decided in the 2026-09-30 audit: publishing also waits on FFI shape, which +relies on Dart being unpublished so its breaking renames are free. + ## Required - [Dart on iOS](/quest/m1/dart-ios.md) - the package advertises iOS, which nobody has run +- [FFI shape](/quest/m1/ffi-shape/README.md) - its breaking renames are free only while Dart is unpublished diff --git a/quest/m1/data-track-clock.md b/quest/m1/data-track-clock.md index f9e1b0cd35..fcb277566b 100644 --- a/quest/m1/data-track-clock.md +++ b/quest/m1/data-track-clock.md @@ -10,6 +10,9 @@ maps its timestamps to a different clock than the media. ## Plan +Remove live() (#4543) is done on `dev`, and the first-frame clock anchor +exists only there, so this targets `dev`. + Have data tracks read the catalog's clock when they stamp, not a copy made when they were created. Keep it crate-private if possible. Test: a data track created before an importer's first frame stamps on the anchored clock. diff --git a/quest/m1/datagram-unfetchable.md b/quest/m1/datagram-unfetchable.md index 264d256331..2de867c657 100644 --- a/quest/m1/datagram-unfetchable.md +++ b/quest/m1/datagram-unfetchable.md @@ -34,9 +34,20 @@ Not planned: reliable or cached datagrams. If they are ever wanted, the way to get them is back-pressure in the QUIC library and treating each datagram like a one-shot stream, not a cache. +The subscription's range bounds datagrams the way it bounds groups, fixed in +the model rather than filtered per session. Decided in the 2026-09-30 audit: +the moxygen line's datagram-range quest merged here, since it duplicated this +late-join rule. Its edge cases: + +- a datagram at the start group when a frame offset skips object 0; +- SUBSCRIBE_UPDATE moving the range while datagrams are in flight; +- a datagram that lands before the subscription's alias or id is known. + Tests: a subscriber joining after datagrams were sent receives only later ones. A FETCH covering a datagram group gets no payload. A fetch stream carrying a datagram-flagged object fails that fetch and leaves the session up. +Each edge case above gets one, and a test tells a datagram filtered by the +range apart from one dropped for any other reason. ## Related diff --git a/quest/m1/drain/README.md b/quest/m1/drain/README.md index 6d527e3740..f1195be9df 100644 --- a/quest/m1/drain/README.md +++ b/quest/m1/drain/README.md @@ -55,3 +55,7 @@ by the stop deadline and encoder reconnect. migrates on GOAWAY with a handover and the guarded redirect the Rust client already has, and the Rust drain path gets its regression test - [JS GOAWAY requests](/quest/m1/drain/js-goaway-requests.md) - after GOAWAY the JS client opens no new request on the old session, like Rust + +## Related + +- [Cluster routing](/quest/m1/cluster-routing/README.md) - the configured topology and link costs a second relay per PoP joins diff --git a/quest/m1/ffi-shape/net.md b/quest/m1/ffi-shape/net.md index 10212aa301..3deba0302b 100644 --- a/quest/m1/ffi-shape/net.md +++ b/quest/m1/ffi-shape/net.md @@ -15,8 +15,14 @@ are renamed. in Rust, since Go gets none; Option fields keep additions additive. This also retires Kotlin `Moq.connect`'s twelve named parameters. - The client config carries the protocol versions to offer, as libmoq's - `moq_client_config.versions` already does (`rs/libmoq/src/api.rs`), so every - binding can pin or restrict versions. moq-ffi has no version setter today. + `moq_client_config.versions` already does (`rs/libmoq/src/client.rs:37`), so + every binding can pin or restrict versions. moq-ffi has no version setter + today. +- The cpp line's client-config quest (`quest/m1/cpp/client-config.md` on + branch `quest/m1/cpp/README`) ships this same `MoqClientConfig` record + additively on `main`. Decided in the 2026-09-30 audit: this quest then only + removes the fallible setters on `dev`, rather than designing the record + twice. - Objects that are only getters become records (`AnnounceUpdate` today). Handles with verbs (`Request`, `TrackRequest`, `GroupRequest`) stay objects. - `TrackProducer` drops `name`/`is_used`/`used`/`unused` for `demand()`. diff --git a/quest/m1/frame-slot-charge.md b/quest/m1/frame-slot-charge.md index cb93048fb9..4b61d31b08 100644 --- a/quest/m1/frame-slot-charge.md +++ b/quest/m1/frame-slot-charge.md @@ -47,3 +47,7 @@ Found by CodeRabbit on #3523, which fixed the one-frame-per-group undercount that was OOM-killing relays serving chat, and deliberately left out of it. Public API: none, unless `MAX_CACHE_BYTES` is restated. Wire: none. + +## Required + +- [Frame alloc budget](/quest/m0/frame-alloc-budget.md) - edits the same group cache charge; land it first diff --git a/quest/m1/hop-aligned-import.md b/quest/m1/hop-aligned-import.md index 065938daa0..9b34dd42b0 100644 --- a/quest/m1/hop-aligned-import.md +++ b/quest/m1/hop-aligned-import.md @@ -21,8 +21,8 @@ encoded stream. Capture is out: two encoders never align. not a per-process counter. Decide how both processes agree across a timestamp wrap (TS PTS wraps every 26.5 h) when they started on opposite sides of it. -- Frame timestamps derive from the input alone too: any re-anchor shift must - come from the stream, not process start or wall clock. +- Frame timestamps derive from the input alone too. Importers publish the + stream's own timestamps and refuse a rewind, so nothing shifts them. - An importer announces only once it knows its tracks, so it never refuses a track the incumbent serves. - Docs (`doc/bin/cli.md` "Redundant publishers", the `--hop` doc comment) @@ -40,5 +40,5 @@ survives the standby joining and the incumbent stopping. ## Related -- [TS import shared shift](/quest/m1/ts-import-shared-shift.md) - the TS re-anchor shift that must stay input-derived +- [Cluster routing](/quest/m1/cluster-routing/README.md) - drops hop lists inside a cluster and must say whether same-hop failover survives; it also owns splicing across first hops and two encoders, which this does not attempt - [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - a redundant pair shares one epoch diff --git a/quest/m1/js-fetch.md b/quest/m1/js-fetch.md index bd58adf0c6..50ac6349eb 100644 --- a/quest/m1/js-fetch.md +++ b/quest/m1/js-fetch.md @@ -40,3 +40,7 @@ A published API break, if the chosen shape requires one, goes through dev. ## Required - [Dynamic track identity](/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md) - settles shared producer identity before adding on-demand group requests + +## Related + +- [Browser archive](/quest/m2/archive-browser.md) - supplies memory or OPFS archive data through this generic request surface diff --git a/quest/m1/legacy-end-overshoot.md b/quest/m1/legacy-end-overshoot.md index 77cff3dc58..6fdd917ab4 100644 --- a/quest/m1/legacy-end-overshoot.md +++ b/quest/m1/legacy-end-overshoot.md @@ -4,7 +4,8 @@ A `@moq/watch` subscriber never aborts a browser-published track with "group timestamp is below the live edge" when the publisher's next group starts -inside the previous group's estimated end. +inside the previous group's estimated end: the JS consumer enforces +monotonic group starts, like Rust. ## Plan @@ -22,13 +23,22 @@ takes the live edge from the end marker, so that keyframe's group reads as below it and the track aborts. The consumer's covered-group skip tolerates the same overlap the live-edge check rejects. +Decided (maintainer, #4543): group starts are monotonic, and that is the only +hard rule. A group's keyframe may not start below the previous group's start, +and no frame may sit below the start of the group before its own. Frames, +keyframes included, may dip below the previous group's content (B-frames, or +a keyframe overlapping its last frame), so an estimated end is never a hard +edge. A group may start at the same timestamp as the previous one; strictly +increasing starts are not enforced. Group IDs never move backwards. A group +starting before the previous group's start is a restart, a new broadcast. +The Rust `moq-mux` producer and consumer enforce this on `dev` since #4543. + - Reproduce with a mocked clock: cut a group, then resume with a keyframe between the last frame and the estimated end. -- Decide which side is wrong: the producer's estimate reaching past what it - will refuse, or the consumer treating an estimated end as a hard edge. - Fix it there and keep the other side's rule consistent with it. -- Check the Rust `moq-mux` producer for the same estimate. +- Align `js/hang/src/container/consumer.ts` (and the JS producer, if it + differs) to "group starts monotonic", matching `rs/moq-mux/src/container`. +- Target `dev`, where the #4543 rule lives. ## Related -- [More tests under load](/quest/m1/test-flakes-2.md) - other load-only failures, fixed at the cause +- [More tests under load](/quest/m1/test-flakes-2/README.md) - other load-only failures, fixed at the cause diff --git a/quest/m1/merge-queue.md b/quest/m1/merge-queue.md index d84f2cee10..a90b27b0d4 100644 --- a/quest/m1/merge-queue.md +++ b/quest/m1/merge-queue.md @@ -17,9 +17,9 @@ and there is no merge queue, so nothing re-runs them on the combined tree. queue tests the combination once, at merge time. - Make the workflows ready: every workflow providing a required check (today `Check` and `Test` in `.github/workflows/check.yml`) also runs on - `merge_group`. `just ci check|test` runs `sh/dispatch.sh`, which scopes - by diffing against its `BASE` argument, else `origin/$GITHUB_BASE_REF`. A - merge group sets no `GITHUB_BASE_REF`, so pass the group's base + `merge_group`. `just ci $JOB $BASE` (`justfile`) scopes by diffing + against its `BASE` argument, else `origin/$GITHUB_BASE_REF`. A merge group + sets no `GITHUB_BASE_REF`, so pass the group's base (`github.event.merge_group.base_sha`) as `BASE`. Check the concurrency group and the `closed`-only skip still behave for queue refs. - Document it in `CONTRIBUTING.md`: PRs merge through the queue, a @@ -29,8 +29,11 @@ and there is no merge queue, so nothing re-runs them on the combined tree. act, after the workflow change lands on `main`. Hand it over with the settings to use rather than changing it. +Decided in the 2026-09-30 audit: no longer waits on the tooling line; the +queue needs only `just ci $JOB $BASE`, which `main` already has. + Public API: none. Wire: none. -## Required +## Related -- [Tooling](/quest/m1/tooling/README.md) - `just ci` and `sh/dispatch.sh`, the entry point the queue runs +- [Tooling](/quest/m1/tooling/README.md) - later changes to the `just ci` entry point the queue runs diff --git a/quest/m1/mobile-ownership.md b/quest/m1/mobile-ownership.md index 14e17f4c94..abfee2ab29 100644 --- a/quest/m1/mobile-ownership.md +++ b/quest/m1/mobile-ownership.md @@ -1,26 +1,22 @@ -# [S] Plan: who owns capture, codecs, and rendering on mobile +# [XS] Rust owns codecs on mobile ## Goal -A written decision, recorded in this tree, on the mobile media boundary: -either Swift and Kotlin own camera, platform codecs, and rendering while Rust -carries encoded access units through the existing media APIs, or Rust owns -capture, codecs, and rendering and `moq-ffi` grows opaque native surface -bridges for `CVPixelBuffer` and Android `HardwareBuffer`/`Surface`. The -capture quests in this questline start only once this is settled. +The mobile media boundary is written down: Rust owns capture, codecs, and +rendering, and `moq-ffi` bridges native surfaces (`CVPixelBuffer` on iOS, +Android `HardwareBuffer`/`Surface`) as opaque handles. Swift and Kotlin do not +grow a parallel platform codec stack. ## Plan -- Option 1 is smaller and idiomatic for an app SDK; option 2 avoids two - parallel media stacks and matches the in-tree direction of moq-video, at - the cost of native-handle ownership across the FFI. #700 lays both out. -- Weigh with evidence rather than preference: what `moq-kit` already does in - Kotlin over `moq-ffi`, what the iroh-live reimplementation says about the - Rust-native audience, and the copy cost of byte-array frames measured on a - device. -- Record the verdict here and in `rs/moq-ffi/AGENTS.md`, and re-estimate - [Android capture](/quest/m2/mobile-capture-android.md) and - [iOS capture](/quest/m2/mobile-capture-ios.md) against it; both target - `moq-video`, so replace them with the required platform-owned implementation - quests if the answer is option 1. Update [mobile completion](/quest/m2/mobile-completion.md) - to require those replacements before abandoning the Rust capture quests. +Decided in the 2026-09-30 audit: option 2 from #700, Rust owns codecs. The +tree already shipped it. `moq-ffi` defaults to the `audio` and `video` +features (`rs/moq-ffi/Cargo.toml:27`), so the Kotlin and Swift packages already +carry the Rust codecs, and #4094 added the `CVPixelBuffer` bridge +(`MoqVideoSurface::PixelBuffer` on `dev`). Option 1 would add a second media +stack beside one that exists. + +What remains is recording the verdict in `rs/moq-ffi/AGENTS.md` (a maintainer +edit) and in [Android capture](/quest/m2/mobile-capture-android.md) and +[iOS capture](/quest/m2/mobile-capture-ios.md), which target `moq-video` as +written and no longer wait on this. diff --git a/quest/m1/moxygen/fetch.md b/quest/m1/moxygen/fetch.md index c62dd2289b..e38d1e533b 100644 --- a/quest/m1/moxygen/fetch.md +++ b/quest/m1/moxygen/fetch.md @@ -21,6 +21,10 @@ cannot express is still an explicit refusal, not a hang. The moxygen FETCH cases that ask for whole groups are the check. The rest of that suite is not. +## Required + +- [Legal IETF input](/quest/m0/ietf-legal-input.md) - decodes the draft-20+ FETCH layout this serves + ## Related - [Moxygen compatibility](/quest/m1/moxygen/README.md) - the line this belongs to diff --git a/quest/m1/obs-moq-video/README.md b/quest/m1/obs-moq-video/README.md index e0712e8ac4..16027a9096 100644 --- a/quest/m1/obs-moq-video/README.md +++ b/quest/m1/obs-moq-video/README.md @@ -8,13 +8,15 @@ Remove the MoQ OBS plugin's dependency on OBS/system FFmpeg ABI versions by deco Portability and FFmpeg removal lead. The current MoQ source decodes video with libavcodec, libavutil, and libswscale, and audio with libavcodec (`moq_source_decode_audio_frame` in `cpp/obs/src/moq-source.cpp`). Its swresample linkage is unused. FFmpeg linkage goes away only once both the video source replacement and the audio decode replacement land. The stranded audio branch (#3498, merged only into `codex/obs-audio-receive-base`) is abandoned; the audio playback quest replans it on the generated C++. The plugin reaches codecs through the generated C++ package over moq-ffi (the migration quest linked below lands first); build `libmoq_ffi` statically with only the codec features needed here; OS frameworks and runtime GPU drivers remain valid dependencies. Verify plugin imports instead of promising a completely static OBS plugin. -Attempt GPU delivery immediately, starting on macOS. Windows and Linux can ship independently. Prefer direct surface reuse, allow GPU conversion/blits, and automatically fall back to CPU delivery when import is unavailable or fails. Stats must show the actual decoder/encoder, delivery path, and fallback reason. Retaining a texture handle is insufficient unless pool ownership and synchronization also prevent reuse while work is in flight. +Attempt GPU delivery immediately on macOS decode; other platforms keep the CPU path here. Prefer direct surface reuse, allow GPU conversion/blits, and automatically fall back to CPU delivery when import is unavailable or fails. Stats must show the actual decoder/encoder, delivery path, and fallback reason. Retaining a texture handle is insufficient unless pool ownership and synchronization also prevent reuse while work is in flight. Initial video decoding covers H.264, HEVC, and AV1 where moq-video has an available backend. Unsupported codecs produce an actionable error; do not retain an FFmpeg fallback. VP8/VP9 return through their own follow-up quest. Audio playback covers Opus, AAC-LC, and PCM. Publishing remains opt-in, with one **Use MoQ encoders** choice for video and audio. Keep the existing OBS encoder mode. Internal OBS encoder adapters call moq-video/moq-audio, preserving OBS's A/V handling and the existing encoded MoQ output. The combined choice is enabled only when both adapters are present. Start with H.264, supported HEVC, and Opus; defer AV1/AAC encoding and PCM publishing UI. Keep bitrate separate from **Low latency**, **Balanced** (default), and **Quality** presets. Presets describe supported buffering/compression controls, not an end-to-end delay promise. -The quests separate portable decoding, platform GPU delivery, audio, and publishing so each can land and be validated independently. moq-ffi's decoded frames own their surface and convert to CPU pixels only on request (#4094, on `dev`); a native decode exposes the platform surface as a borrowed view, which each platform quest extends to its own surface type. +The quests separate portable decoding, audio, and publishing so each can land and be validated independently. moq-ffi's decoded frames own their surface and convert to CPU pixels only on request (#4094, on `dev`); a surface decode exposes the platform surface as a borrowed view, which each platform GPU quest extends to its own surface type. + +Decided in the 2026-09-30 audit: the Windows and Linux GPU decode paths and the macOS and Windows GPU encoder inputs moved to m2 (listed under Related), because each needs physical-hardware proof, and Linux GPU input is already m2/m3. ## Required @@ -30,6 +32,12 @@ The quests separate portable decoding, platform GPU delivery, audio, and publish ## Related -- [OBS migration](/quest/m1/cpp/obs.md) - every quest here starts from the plugin on the generated C++, so codec surface is designed in moq-ffi and reaches libmoq and the other wrappers through the Cross-Package Sync table, not as OBS-only C symbols +- [OBS migration](/quest/m1/cpp/obs.md) - every quest here starts from the plugin on the generated C++, so codec surface is designed in moq-ffi and reaches the other bindings through the Cross-Package Sync table, not as OBS-only C symbols +- [Linux GPU decode](/quest/m2/obs-decode-linux.md) - present hardware-decoded frames without CPU readback; needs physical-hardware proof +- [Windows GPU decode](/quest/m2/obs-decode-windows.md) - present D3D11 decoded textures without CPU readback; needs physical-hardware proof +- [macOS GPU input](/quest/m2/obs-macos.md) - feed compositor frames to VideoToolbox without a CPU round trip; needs physical-hardware proof +- [Windows GPU input](/quest/m2/obs-windows.md) - feed D3D11 compositor frames to the encoder without CPU staging; needs physical-hardware proof - [Video hardware validation](/quest/m3/video-hardware.md) - physical hardware evidence is required for each claimed GPU path - [Audio codecs](/quest/m1/audio-codecs/README.md) - HE-AAC, multichannel, and native AAC encode reach the OBS source and encoder adapters through moq-ffi +- [Linux GPU input](/quest/m3/obs-linux-gpu.md) - allocation-export feasibility and its dependent implementation are deferred +- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - owns Linux backend decode/import capabilities; reconcile its older dependency assumptions against current code diff --git a/quest/m1/obs-moq-video/linux-bundle.md b/quest/m1/obs-moq-video/linux-bundle.md index a72503ca7c..7342b24611 100644 --- a/quest/m1/obs-moq-video/linux-bundle.md +++ b/quest/m1/obs-moq-video/linux-bundle.md @@ -9,10 +9,14 @@ Every `obs-moq-v*` release attaches a Linux x86_64 tarball that loads into a sto - The only reason `obs-build` in `.github/workflows/libmoq.yml` skips Linux is FFmpeg: the source links nix/distro libavcodec for both video and audio, which is not portable. The FFmpeg removal (video source replacement plus audio playback) is the blocker; once the plugin is C++ over moq-ffi plus libobs and Qt6, a Linux build has no extra runtime dependency that OBS itself does not already carry. - Build on `ubuntu-24.04` (glibc 2.39, the floor OBS's own Linux packages target) against the libobs and Qt6 headers OBS's plugin template uses; the template's `.deb` recipe is the reference. Ship the plain archive layout the other platforms use (`obs-moq-*-x86_64-unknown-linux-gnu.tar.gz` with `bin/64bit/obs-moq.so` and `data/`), extractable into `~/.config/obs-studio/plugins/obs-moq/`. A `.deb` is optional and separate. - Flatpak OBS cannot load a plugin from the host filesystem. Verify a real Flatpak install and support it only if the sandbox's runtime ABI matches, documenting the `~/.var/app/com.obsproject.Studio/config/obs-studio/plugins/` path. Otherwise, state plainly that Flatpak is unsupported. -- `cpp/obs/build.sh --target x86_64-unknown-linux-gnu` produces the tarball, and the matrix in `obs-build` gains the row; nothing else in the release pipeline changes. Keep the nightly `just obs ci` compile as the PR gate; #4370 (open) compiles the OBS plugin on every PR, which covers the Linux compile if it lands first. +- `cpp/obs/build.sh --target x86_64-unknown-linux-gnu` produces the tarball, and the matrix in `obs-build` gains the row; nothing else in the release pipeline changes. The Linux compile gate already exists: #4370 (merged) compiles the OBS plugin on every PR. - Verify by loading the tarball into the oldest supported OBS 32 release and current stable on Ubuntu 24.04 and one non-Debian distro (Fedora), publishing and subscribing against a relay, and inspecting the `.so` with `ldd` for nothing beyond libobs, Qt6, glibc, and OS libraries. ## Required - [Video source replacement](/quest/m1/obs-moq-video/source.md) - removes the FFmpeg video linkage that makes a Linux binary non-portable - [Audio playback](/quest/m1/obs-moq-video/audio-playback.md) - removes the FFmpeg audio linkage + +## Related + +- [Linux decoded frames](/quest/m2/obs-decode-linux.md) - native surface delivery lands on top of the portable CPU path this bundle ships diff --git a/quest/m1/obs-moq-video/source.md b/quest/m1/obs-moq-video/source.md index e0178523f9..e7bf0fd977 100644 --- a/quest/m1/obs-moq-video/source.md +++ b/quest/m1/obs-moq-video/source.md @@ -7,7 +7,7 @@ The MoQ source loads and plays supported video without FFmpeg's video libraries ## Plan - Replace `cpp/obs/src/moq-source.cpp` video decode and conversion with moq-video through the generated C++ package: the moq-ffi video consumer for the CPU path, including frame ownership and cancellation semantics. Support H.264, HEVC, and available AV1 decoding; report unsupported VP8/VP9 explicitly until their follow-up lands. -- Decode with `MoqVideoDecoderOutput.native` set: each `MoqVideoDecodedFrame` retains the decoder's surface, `native()` borrows it (`PixelBuffer` on macOS) for as long as the frame lives, and `pixels(format)` is the CPU fallback. Hold the frame until OBS's GPU work reading it completes; held frames hold decoder pool slots. This surface landed on `dev` (#4094). +- Decode with `MoqVideoDecoderOutput.surface` set: each `MoqVideoDecodedFrame` retains the decoder's surface, `surface()` borrows it (`MoqVideoSurface::PixelBuffer` on macOS) for as long as the frame lives, and `pixels(format)` is the CPU fallback. A platform with no `MoqVideoSurface` variant refuses the opt-in. Hold the frame until OBS's GPU work reading it completes; held frames hold decoder pool slots. This surface is on `dev` only (#4094, renamed in 97575f002). - Implement the macOS presentation probe immediately: retain VideoToolbox PixelBuffer/IOSurface storage, inspect OBS graphics import and rendering support, and convert to OBS's expected color format on the GPU if necessary. Adapt the source render path to import textures on the graphics thread; preserve source timing instead of simply drawing the newest frame. An asynchronous CPU source API alone does not prove native GPU delivery. - Bound decoded frames retained by the render thread. On import failure, switch to the existing I420 delivery path and show the reason in Stats. Keep fallback stable for the stream/device configuration rather than retrying every frame; re-probe on a relevant configuration change or restart. Device loss and resize must retire old surfaces only after rendering completes. - Preserve timestamps, range/primaries, stride and plane layout, catalog/rendition changes, reconnect, visibility/deactivation behavior, and existing source settings. Carry frame-generation identity so late callbacks cannot display frames from a replaced source. @@ -17,6 +17,7 @@ The MoQ source loads and plays supported video without FFmpeg's video libraries ## Required - [OBS migration](/quest/m1/cpp/obs.md) - the plugin is on the generated C++ before decode changes +- dev's decoded-frame surface (#4094, 97575f002) reaches main ## Related diff --git a/quest/m1/obs-moq-video/vpx.md b/quest/m1/obs-moq-video/vpx.md index 528d73b124..6dbc9ed8a9 100644 --- a/quest/m1/obs-moq-video/vpx.md +++ b/quest/m1/obs-moq-video/vpx.md @@ -14,4 +14,4 @@ MoQ consumers, including the OBS source, decode VP8 and VP9 without depending on ## Related - [Video source replacement](/quest/m1/obs-moq-video/source.md) - consumer integration, not a blocker for codec implementation -- [Color model](/quest/m1/color-model.md) - share codec-neutral color metadata rather than introducing VP9-only conversions +- [Catalog colour model](/quest/m2/color-catalog.md) - share codec-neutral color metadata rather than introducing VP9-only conversions diff --git a/quest/m1/origin-front-parks.md b/quest/m1/origin-front-parks.md index e31c28fa4e..729c9c1d82 100644 --- a/quest/m1/origin-front-parks.md +++ b/quest/m1/origin-front-parks.md @@ -28,3 +28,11 @@ show the replacement is cheaper. Public API: no signature change expected. `routed_broadcast` and `request_broadcast` keep their contracts; only where the waiting happens changes. + +Decided in the 2026-09-30 audit: this lands after shared fronts, which +reworks the same `model/origin.rs` fronts, and may move to m2 if shared +fronts' benchmark shows the retry loop's re-mint is noise. + +## Required + +- [Shared fronts](/quest/m0/shared-fronts.md) - reworks the same fronts, and its benchmark decides whether this stays in m1 diff --git a/quest/m1/parked-read-wakes.md b/quest/m1/parked-read-wakes.md index 2e372aff46..3e403f8b26 100644 --- a/quest/m1/parked-read-wakes.md +++ b/quest/m1/parked-read-wakes.md @@ -22,4 +22,12 @@ Follow-ups from [#4484](https://github.com/moq-dev/moq/pull/4484), which made Settled: one quest, since both are the same wakeup invariant in `rs/moq-net/src/model/track.rs` and `resume.rs`. +Land after [resume latest](/quest/m1/resume-latest.md) (#4491), which edits +the same `resume.rs` wakeups, and rebase onto it. + Public API: none. Wire: none. + +## Related + +- [Resume latest](/quest/m1/resume-latest.md) - edits the same `resume.rs` wakeups; lands first (#4491) +- [#2991](/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md) - extends the `resume.rs` takeover tests diff --git a/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md b/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md index a5fccaa027..4d9317643d 100644 --- a/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md +++ b/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md @@ -35,7 +35,7 @@ Where the reads are: `close` (rs/moq-uring/src/quic/noq/connection.rs:239), `handle_timeout` (:671), and `poll_transmit` (:786). The last one runs once per GSO train, since `flush` stages one train per turn (see - [Egress requeue](/quest/m1/perf/egress-requeue.md)). + [Run to quiescence](/quest/m1/perf/uring-quiescence.md)). The same profile shows the timer heap at ~1.6%: `::set` 0.92% plus diff --git a/quest/m1/perf/README.md b/quest/m1/perf/README.md index b8610dfade..4eb4d4ad0f 100644 --- a/quest/m1/perf/README.md +++ b/quest/m1/perf/README.md @@ -23,7 +23,7 @@ hot-path survey, so quests don't re-litigate them: - `moq-uring`'s only backend is noq. Every profile names its backend. The historical quiche-flavor numbers cited in - [Egress requeue](/quest/m1/perf/egress-requeue.md) and + [Run to quiescence](/quest/m1/perf/uring-quiescence.md) and [#3122](/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md) are re-measured on noq. - Cross-thread wakeups are already cheap: one futex word per worker, at most @@ -43,13 +43,26 @@ The relay's `/metrics` endpoint already carries the ring-level counters (enters, park/wake, batch effectiveness) several quests want as evidence, one row per io_uring worker. +Decided in the 2026-09-30 audit: io_uring is off by default and ships in no +package, and none of the uring micro-opts is measured on noq. The unmeasured +ones moved out (3129, 3200, 3202, the open contract, and cache shard to m2; +3201 and 3204 to m3), and NAPI busy polling (3203), registered wait +arguments (3205), and the priority `set_track` wakes were dropped. Egress +requeue folded into Run to quiescence; egress keep-alive and owned decode +copies folded into Group cost. The benchmark noise estimate and the noq +re-profile come first, since every quest here accepts "within noise". Lock +wait and One enter per turn rank next: they produce the numbers that decide +the rest. + ## Required -- [Announce replay](/quest/m1/perf/announce-replay.md) - the initial announce set replays in linear time, so joins don't slow with the route count -- [Group cost](/quest/m1/perf/group-cost.md) - count and cut the allocations and time spent relaying one small group to one viewer -- [One enter per turn](/quest/m1/perf/uring-one-enter.md) - a parking turn pays one io_uring_enter, submits flush deferred completions, and SQEs per enter is a counter -- [Run to quiescence](/quest/m1/perf/uring-quiescence.md) - a received packet's reply is staged in the same turn, under a pass budget that keeps the fairness rule +- [Performance comparisons](/quest/m1/performance-comparisons.md) - the noise estimate every "within noise" verdict here depends on +- [Performance profiles](/quest/m1/performance-profiles.md) - the reproducible noq profile the quests below re-measure on - [Lock wait](/quest/m1/perf/lock-wait.md) - each worker reports time blocked on cross-worker locks, deciding whether the shared model needs work +- [One enter per turn](/quest/m1/perf/uring-one-enter.md) - a parking turn pays one io_uring_enter, submits flush deferred completions, and SQEs per enter is a counter +- [Group cost](/quest/m1/perf/group-cost.md) - count and cut the allocations and time spent relaying one small group to one viewer +- [Run to quiescence](/quest/m1/perf/uring-quiescence.md) - a received packet's reply is staged in the same turn, under a pass and train budget that keeps the fairness rule +- [Announce replay](/quest/m1/perf/announce-replay.md) - the initial announce set replays in linear time, so joins don't slow with the route count - [Ingest batch](/quest/m1/perf/ingest-batch.md) - relay ingest pays one lock, wake, and clock read per chunk burst instead of per chunk - [#3122](/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md) - moq-uring: ~2.5% of relay CPU is vdso clock reads; the drive loop and its callers each re-read Instant::now() - [#3199](/quest/m1/perf/3199-moq-uring-remove-sq-indirection-and-per-enter-ring-fd.md) - moq-uring: remove SQ indirection and per-enter ring fd lookup diff --git a/quest/m1/perf/group-cost.md b/quest/m1/perf/group-cost.md index e3679232df..9dea750f03 100644 --- a/quest/m1/perf/group-cost.md +++ b/quest/m1/perf/group-cost.md @@ -24,3 +24,18 @@ keeps that waiter and skips re-registering on lists that still hold it (2026-09: 228 to 122 allocations per viewer-group, 352 to 118 paced). Find the next largest source from there. A measured no-win abandons the quest, per this line's rules. + +Decided in the 2026-09-30 audit: two suspected per-group costs merged here +as candidates to measure with `SESSION_ALLOCS`, not separate quests. + +- Egress cache refresh: `group::Consumer::keep_alive` takes a state read + guard and calls `Charge::refresh` between completed frame writes on both + the lite and IETF publishers. It protects a drain longer than + `latency_max`, so keep per-frame liveness and + `slow_prefetch_reader_survives_expiry` (rs/moq-net/src/model/track.rs); + fewer refresh calls alone are not a win. +- Owned decode copies: `rs/moq-net/src/coding/decode.rs` decodes `Vec` + through `Buf::copy_to_bytes` then `to_vec`, and `String` consumes that + vector. Measure on the real reader input types (contiguous and chained) + before assuming a copy; keep the owned return types, bounds checks, and + UTF-8 validation. diff --git a/quest/m1/perf/lock-wait.md b/quest/m1/perf/lock-wait.md index 149134388e..e4e922bf7c 100644 --- a/quest/m1/perf/lock-wait.md +++ b/quest/m1/perf/lock-wait.md @@ -26,3 +26,7 @@ fix in the shared model rather than the runtime; this quest only measures. Below one percent, record it and close the quest. Above, open a quest with the measured hot locks named, and only then decide between submitting staged SQEs before a blocking acquire and shrinking the lock in the model. + +Decided in the 2026-09-30 audit: [Cache shard](/quest/m2/cache-shard.md) +stays in m2 and is taken up only if this shows more than 1% contention on +the pool line. diff --git a/quest/m1/perf/uring-one-enter.md b/quest/m1/perf/uring-one-enter.md index d3c3a8ea64..c6238cdf53 100644 --- a/quest/m1/perf/uring-one-enter.md +++ b/quest/m1/perf/uring-one-enter.md @@ -44,3 +44,5 @@ fails without the flag. Latency must not regress. - [Run to quiescence](/quest/m1/perf/uring-quiescence.md) - fewer turns per packet, which multiplies this saving +- [#3200](/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md) - + the wait side of the same enter diff --git a/quest/m1/perf/uring-quiescence.md b/quest/m1/perf/uring-quiescence.md index 5ff5a19ba6..ba226bd729 100644 --- a/quest/m1/perf/uring-quiescence.md +++ b/quest/m1/perf/uring-quiescence.md @@ -29,11 +29,17 @@ must not starve the socket. dispatch, then tasks to quiescence, then submit or park. That removes the stale first pass a park-returning turn runs today (worker.rs:180 polls on readiness the previous turn already consumed). -- The egress driver's one-train-then-self-wake shape - (rs/moq-uring/src/quic/noq/connection.rs:743-751) interacts with the - budget: a deep backlog would consume every pass. Fold the - [egress requeue](/quest/m1/perf/egress-requeue.md) train budget into the - same sweep so trains per turn and passes per turn are measured together. +- The egress driver stages one GSO train of `TRAIN_SEGMENTS = 63` segments + per turn and then wakes itself (`Driver::flush`, + rs/moq-uring/src/quic/noq/connection.rs:743-751), so a deep backlog pays a + whole turn per train and would consume every pass. That cadence is a + hardcoded fairness choice across connections sharing a socket. Make it a + trains-per-turn budget on `flush` and sweep 1, 2, and 4 together with the + pass budget, under the fanout and single-heavy-connection shapes, so trains + per turn and passes per turn are measured together. A no-win keeps 1 train. + The #3120 numbers are the deleted quiche driver's; re-profile on noq. + Decided in the 2026-09-30 audit: the egress requeue quest merged here, since + both budgets need the same sweep. - Add `passes` per turn to the metrics beside `turns`. Acceptance: turns and enters per received datagram on the chat shape diff --git a/quest/m1/performance-comparisons.md b/quest/m1/performance-comparisons.md index ab8a640e72..9512e1b2cb 100644 --- a/quest/m1/performance-comparisons.md +++ b/quest/m1/performance-comparisons.md @@ -7,14 +7,17 @@ estimate and preserved evidence, so a small reported speedup can be evaluated. ## Plan -`bench/run.sh` runs each relay workload once as base then current, -without repeated rounds or alternating execution order. `cleanup` deletes the run +`bench/run.sh --runtime` already repeats rounds (`MOQ_BENCH_RUNTIME_ROUNDS`, +default 3) and reports the median (`bench/run.sh:15`, `:322`), but the +`just bench BASE` comparison still runs each relay workload once as base then +current, without alternating execution order. `cleanup` deletes the run directory, including Criterion estimates, load/host JSONL, relay logs, and -summaries. Preserve the existing default command -while extending this harness rather than creating another benchmark runner. +summaries. Preserve the existing default command while extending this harness +rather than creating another benchmark runner. -- Add configurable repeated paired rounds, alternate base/current order, and - perform warmup outside the measured window. Keep the current load generator, +- Reuse the runtime mode's rounds and median for the BASE comparison, as + paired rounds that alternate base/current order, and perform warmup outside + the measured window. Keep the current load generator, workload, backend, and resolved settings identical for both revisions. - Save individual paired results and report median paired deltas plus a documented dispersion/confidence estimate. Flag insufficient or noisy samples instead of diff --git a/quest/m1/plan-watch-worker.md b/quest/m1/plan-watch-worker.md index 5e39f90a19..baf471e177 100644 --- a/quest/m1/plan-watch-worker.md +++ b/quest/m1/plan-watch-worker.md @@ -75,3 +75,4 @@ follow-up wires the capture worklet to the encoder worker with a - [Watch worker](/quest/m1/watch-worker.md) - the implementation this rewrites - [Browser benchmarks](/quest/m1/browser-benchmarks.md) - artifact conventions; may absorb the harness later +- [A/V clock](/quest/m1/av-clock.md) - the `Sync` shape the implementation moves; the prototype can pace against today's diff --git a/quest/m1/quic/README.md b/quest/m1/quic/README.md index 251cdf92da..d09e79e75b 100644 --- a/quest/m1/quic/README.md +++ b/quest/m1/quic/README.md @@ -6,12 +6,12 @@ MoQ owns the QUIC features it needs. noq, the Quinn-derived stack n0 maintains for Iroh, is the parent; the moq-dev fork carries what MoQ needs on MoQ's schedule and offers it upstream when it is general. One core serves the tokio backend, the thread-per-core `moq-uring` backend, iroh, and qmux. The -features are per-stream acknowledgment progress, reliable stream resets, -hierarchical stream scheduling with per-broadcast fairness, the shared stream -state machine used by qmux, per-stream deadlines, and wider limits for relay -peers. The experiments that may join them (GCC, FEC, receive timestamps, -kernel pacing, buffer pools, media probing, L4S, careful resume, deadline -keep-alive) live in [m2](/quest/m2/README.md) and do not gate this line. +features are BBR correctness, reliable stream resets, hierarchical stream +scheduling with per-broadcast fairness, and wider limits for relay peers. +Per-stream acknowledgment progress, per-stream deadlines, qmux on the shared +stream state machine, and the experiments (GCC, receive timestamps, the egress +profile, media probing, L4S, careful resume, deadline keep-alive) live in +[m2](/quest/m2/README.md) and do not gate this line. ## Plan @@ -28,8 +28,9 @@ with only what each needs, rather than adding another simulation loop; a fix at the transport boundary still needs a transport test through the real callbacks. The existing loops stay, since the fork merges upstream weekly and a port would conflict. Each BBR fix ships in a fork patch release without -waiting for the remaining transport features. The -[Google comparison](/quest/m2/quic-bbr-google.md) is a separate study. +waiting for the remaining transport features. The comparison against +Google's BBR is part of the separate +[natural drain study](/quest/m2/quic-bbr-natural-drain.md). Rules the line keeps: @@ -38,6 +39,8 @@ Rules the line keeps: never a crate that impersonates the parent; - published MoQ crates depend on crates.io releases of the fork, never a workspace-only Cargo patch or a mutable branch; +- each feature quest releases the fork crate it changes and records the + parent noq commit it is based on; there is no separate release quest; - MoQ's config names congestion families (`Loss`, `Delay`, and `RealTime` once GCC ships), never algorithms; noq's public `Controller` trait is the seam experiments plug into, and MoQ owns which algorithm each family means. @@ -49,6 +52,12 @@ session with fairness enabled, the send group is the broadcast. The default MoQ order is newest group first; an ordered subscription keeps oldest first. This is a transport API change, not a MoQ wire change. +Decided in the 2026-09-30 audit: deadlines, qmux, BBR loss parity, ECN +measurement, ACK progress, and the ACK hook moved to m2, since no m1 quest +consumes them. The release quest was deleted: the fork already publishes to +crates.io (moq-noq 1.3.2, web-transport-moq 2.0.0) with no workspace patch, +and the qmux crate release folds into [qmux](/quest/m2/quic-qmux.md). + ## Required - [BBR idle burst](/quest/m1/quic/bbr-app-limited.md) - a fork regression proves a burst after a long idle is paced at the learned bandwidth, closing #4219 @@ -77,3 +86,7 @@ This is a transport API change, not a MoQ wire change. - [L4S on the backbone](/quest/m2/quic-ecn.md) - an ECT(1) option in the fork, an `ecn` config knob, and a dualpi2 measurement - [Careful resume on reconnect](/quest/m2/quic-careful-resume.md) - a redial starts at the previous connection's rate - [Keep-alive by deadline](/quest/m2/quic-keep-alive.md) - a PING only when the idle deadline nears, no fixed timer +- [Receive timestamps](/quest/m3/quic-receive-ts.md) - per-packet arrival + times for GCC and deadlines +- [GCC egress experiment](/quest/m3/quic-gcc.md) - a measured verdict on + WebRTC-style delay control diff --git a/quest/m1/quic/reliable-reset.md b/quest/m1/quic/reliable-reset.md index 22354b9dca..64becb43ca 100644 --- a/quest/m1/quic/reliable-reset.md +++ b/quest/m1/quic/reliable-reset.md @@ -70,3 +70,5 @@ provisional codepoints if the document changes before release. a grace for a group whose reset lost its header until this lands - The removed quiche backend was the one stack that had this, so it is the known browser-compliance gap. +- [qmux on the QUIC stream state machine](/quest/m2/quic-qmux.md) - consumes + the same reset state without a parallel implementation diff --git a/quest/m1/quic/scheduler.md b/quest/m1/quic/scheduler.md index 9745d5e96f..c28b33fb3a 100644 --- a/quest/m1/quic/scheduler.md +++ b/quest/m1/quic/scheduler.md @@ -117,3 +117,5 @@ where the new implementation makes it redundant. - [Scope track priority](/quest/m1/track-priority-scope.md) - owns the priority semantics this mechanism realizes, including the scheduling-domain scope +- [Signed priority](/quest/m2/signed-priority.md) - changes the priority type + this orders on; keep the ordering, not just the type diff --git a/quest/m1/quic/upstream.md b/quest/m1/quic/upstream.md index 97a6c22758..796f087fd9 100644 --- a/quest/m1/quic/upstream.md +++ b/quest/m1/quic/upstream.md @@ -40,22 +40,23 @@ Then the feature proposal order, each linked to its producing quest: 4. per-stream deadlines ([deadlines](/quest/m2/quic-deadline.md)); 5. the qmux crate over the shared stream state machine ([qmux](/quest/m2/quic-qmux.md)). -The m2 features (keep-alive by deadline, careful resume as a `Controller` +Items 1, 4, and 5 moved to m2 in the 2026-09-30 audit; like the other m2 +features they are offered when they land and do not gate this quest. The +other m2 features (keep-alive by deadline, careful resume as a `Controller` wrapper, ECT(1) marking, the media-headroom mechanism) are offered when they land, but do not gate this quest: an m1 quest must not wait on m2 work. The -next experiments (receive timestamps, GCC, FEC, kernel pacing, send -batching, buffer pools, the natural-drain check) join the list only with a +next experiments (receive timestamps, GCC, the egress profile, the +natural-drain check) join the list only with a positive verdict. Record in this quest what upstream accepted, what it asked to see as an extension crate, and what it declined; a declined change stays in the fork -with the link beside it. The quest completes when the list above has been -offered and answered. +with the link beside it. The quest completes when the m1 items above have +been offered and answered. ## Required - [Mark BBR starvation wherever the source runs dry](/quest/m1/quic/bbr-app-limited-edges.md) - - [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) @@ -66,3 +67,6 @@ offered and answered. [L4S on the backbone](/quest/m2/quic-ecn.md), [Discover media headroom](/quest/m2/quic-probe.md) - m2 features offered upstream when they land +- [BBR media study](/quest/m2/quic-bbr-natural-drain.md), + [Receive timestamps](/quest/m3/quic-receive-ts.md), [GCC](/quest/m3/quic-gcc.md) - + experiments that join the list with a positive verdict diff --git a/quest/m1/quic/uring-close.md b/quest/m1/quic/uring-close.md index f3ac2ab58f..68971a73f6 100644 --- a/quest/m1/quic/uring-close.md +++ b/quest/m1/quic/uring-close.md @@ -16,3 +16,9 @@ Reproduce on a Linux kernel supporting the io_uring worker, both in isolation and alongside the full test suite. Determine where the final close is lost between QUIC output, UDP submission, and worker teardown. Preserve the existing close contract; do not mask the failure with a retry or a longer timeout. + +## Plan + +#4431 (2f4b78581) fixed the same root cause, a paced CONNECTION_CLOSE lost +at teardown. Re-run this test under the #4431 stress setup (8 stress-ng hogs +on one CPU); if it passes, delete this quest. diff --git a/quest/m1/raw-stream-codes.md b/quest/m1/raw-stream-codes.md index 0cd61c7f85..7ddf9891e3 100644 --- a/quest/m1/raw-stream-codes.md +++ b/quest/m1/raw-stream-codes.md @@ -15,6 +15,11 @@ on stream errors. WebTransport sessions keep the mapping they need. `web-transport-iroh` and `web-transport-quinn` (moq-dev/web-transport) do the same. A raw peer's code 5 reads as `None` or another value, and ours reaches it as a large HTTP/3 code. +- Close codes (#4262, merged on `dev` 09-28) fixed the same mix-up for + `ApplicationClosed` and brought `web-transport-trait` 0.5, which exists + only on `dev`, so this targets `dev`. Let each stream know whether its + session is raw and skip the mapping there, in all three adapters, with a + round-trip test per adapter against a plain QUIC peer. - Release the fixed crates and bump the pins here in the same quest; published crates depend on crates.io releases, never a patch. A moq-tokio test over `moqt://` asserts a reset code arrives verbatim, beside `close_code.rs`. @@ -28,7 +33,7 @@ on stream errors. WebTransport sessions keep the mapping they need. a legacy-sender test per adapter. The other direction has no fix at the receiver: an older peer misreads a fixed peer's raw codes. Check which codes moq-net acts on (group stream resets, subscribe STOP_SENDING): if any drives - behaviour beyond reporting, this is a wire break and retargets to `dev`. + behaviour beyond reporting, this is a wire break as well. Public API: none expected. Wire: raw QUIC stream error codes become the application's own values; compatible only if older peers merely report them. diff --git a/quest/m1/relay-auth-client-ca.md b/quest/m1/relay-auth-client-ca.md index 3462393429..3dec129961 100644 --- a/quest/m1/relay-auth-client-ca.md +++ b/quest/m1/relay-auth-client-ca.md @@ -10,7 +10,8 @@ config instead of quietly refusing every session. ## Plan -- [#4364](https://github.com/moq-dev/moq/pull/4364) adds an additive +- [#4364](https://github.com/moq-dev/moq/pull/4364) (merged 09-28, and + `dev` has merged `main` since) added an additive `Config::validate_client_ca(&self, client_ca: bool)` that `Relay::load` and the CLI must each remember to call; the CLI missing the original check is the bug it fixes. Fold it into `validate(&self, client_ca: bool)` so every @@ -34,8 +35,3 @@ config instead of quietly refusing every session. Public API: breaks `moq_relay::auth::Config::validate` and `init`, removes `validate_client_ca`, so this targets `dev`. Wire: none. - -## Required - -- #4364 merged to `main` -- `dev` has merged `main` after #4364 lands diff --git a/quest/m1/relay-iroh-opt-in.md b/quest/m1/relay-iroh-opt-in.md index 87b94ebc80..522796d96f 100644 --- a/quest/m1/relay-iroh-opt-in.md +++ b/quest/m1/relay-iroh-opt-in.md @@ -23,3 +23,8 @@ Guidance: ignored, whether it comes as a flag, an environment variable, or TOML. - Update `doc/bin/relay/` and any example that relies on the relay's iroh listener. Report the binary size difference in the PR. + +## Related + +- [`moq relay`](/quest/m2/moq-relay-subcommand.md) - forwards the relay's features from moq-cli's +- [P2P](/quest/m2/p2p/README.md) - why moq-cli keeps iroh diff --git a/quest/m1/resume-latest.md b/quest/m1/resume-latest.md index fb878c5c1a..3949bfec69 100644 --- a/quest/m1/resume-latest.md +++ b/quest/m1/resume-latest.md @@ -71,3 +71,5 @@ Regression tests, on a paused clock (`#[tokio::test(start_paused = true)]`): ## Related - [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - tells a resumed latest group when the new copy dropped it +- [Parked reads wake](/quest/m1/parked-read-wakes.md) - edits the same `resume.rs` wakeups; lands after this quest's #4491 +- [#2991](/quest/m1/2991-net-coalesce-dynamic-tracks-and-preserve-sequences-across.md) - extends the same `resume.rs` takeover tests diff --git a/quest/m1/rs2ts/README.md b/quest/m1/rs2ts/README.md index f152d90870..b89bcd59c4 100644 --- a/quest/m1/rs2ts/README.md +++ b/quest/m1/rs2ts/README.md @@ -5,7 +5,7 @@ moq-net is the single implementation of the MoQ protocol and model layer. The browser runs it as TypeScript generated from the Rust source, retiring js/net's hand-written equivalent with no regression in bundle size, CPU, or -usability. Lite comes first, IETF after. Transport glue (the WebTransport and +usability. Lite is this line; IETF follows in m2. Transport glue (the WebTransport and WebSocket pumps, timers) stays hand-written TypeScript. ## Plan @@ -48,12 +48,24 @@ Decided in planning (2026-09-27), with the spike data in - Hand-written js/net fixes keep landing until the generated path replaces them; it is months out. +Decided in the 2026-09-30 audit: the lite half stays in m1 with an explicit +go/no-go after the no-downgrade report below; a no-go stops the line before +anything else is generated. The IETF half (the sans-IO IETF session, +generated IETF, and the IETF parameters quest on this line's branch) moved to +m2 and waits on that go. + +PR #4455 (branch `quest/m1/rs2ts/lite-leading-ones`) changes the lite-07 +varint wire and no quest tracks it; it coordinates with +[subscribe drop](/quest/m1/subscribe-drop.md)'s lite-07 edits. + This README's own work is the no-downgrade report once generated lite ships: bundle size, per-frame CPU, and first-frame latency against the hand-written js/net it replaces, measured with the [browser benchmarks](/quest/m1/browser-benchmarks.md). ## Required +- [Browser benchmarks](/quest/m1/browser-benchmarks.md) - the harness the no-downgrade report uses + - [VarInt codec](/quest/m1/rs2ts/varint-codec.md) - moq-net encodes through a `VarInt` newtype and a concrete slice-based codec, not generic traits on primitives - [rs2ts](/quest/m1/rs2ts/translator.md) - a Charon-based translator emits readable TypeScript for moq-net's lite codec, committed and checked for drift in CI - [Sans-IO moq-net](/quest/m1/rs2ts/sans-io/README.md) - moq-net builds and runs without a runtime; async helpers sit behind an `async` feature @@ -67,10 +79,7 @@ js/net it replaces, measured with the [browser benchmarks](/quest/m1/browser-ben - [#2822](https://github.com/moq-dev/moq/issues/2822) - close this issue when the quest finishes - [#2835](https://github.com/moq-dev/moq/issues/2835) - close this issue when the quest finishes -## Required - -- [Browser benchmarks](/quest/m1/browser-benchmarks.md) - the harness the no-downgrade report uses - ## Related -- [#2850](/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md) - the same synchronous decode shape, in hand-written js/net today +- [#2850](/quest/m1/2850-js-net-give-reader-a-synchronous-decode-so-the-publisher.md) - caps hand-written js/net's subscription controls; generated lite replaces the rest +- [Generated IETF](/quest/m2/rs2ts-ietf.md) - the IETF half, deferred to m2 until the lite go/no-go diff --git a/quest/m1/rs2ts/sans-io/README.md b/quest/m1/rs2ts/sans-io/README.md index 2795d86d47..a74c9af8bb 100644 --- a/quest/m1/rs2ts/sans-io/README.md +++ b/quest/m1/rs2ts/sans-io/README.md @@ -13,10 +13,16 @@ reimplements the async helpers natively with Promises, so the translator reads the crate without the `async` feature. Split by layer so each lands on `dev` independently. -The line has no work of its own beyond its children. +The line has no work of its own beyond its children. Decided in the +2026-09-30 audit: the IETF session moved to m2 with the rest of the IETF +half; this line is lite and the model only. ## Required - [Sans-IO lite session](/quest/m1/rs2ts/sans-io/lite.md) - the lite session is driven by bytes, stream events, and `tick(now)` - [Sans-IO model](/quest/m1/rs2ts/sans-io/model.md) - origin, broadcast, track, and group handles run without a runtime, with time supplied by the caller - [The async feature](/quest/m1/rs2ts/sans-io/async-feature.md) - the async helpers sit behind an `async` feature and a CI lane builds and tests moq-net without it + +## Related + +- [Sans-IO IETF session](/quest/m2/rs2ts-sans-io-ietf.md) - the moq-transport session, deferred to m2 diff --git a/quest/m1/stats/README.md b/quest/m1/stats/README.md index 02d31b0fc0..3bd008a5b2 100644 --- a/quest/m1/stats/README.md +++ b/quest/m1/stats/README.md @@ -7,8 +7,7 @@ that wants to hear from its viewers can solicit feedback there too. The publisher's `stats` track is one snapshot of what it sent, per rendition and for its connection. A viewer publishes one `.echo` broadcast per soliciting catalog it reads, carrying what it received and played, per rendition, and -its own connection. A dashboard reads both the same way a publisher does, and -a Rust encoder adapts its bitrate to what its viewers report. Stats and +its own connection. A dashboard reads both the same way a publisher does. Stats and feedback cost nothing on the network unless someone subscribes. Not here: the relay's `moq-stats` layout, which stays as it is; clock synchronization; any requirement that a client report; and feedback as an input to billing, @@ -68,10 +67,7 @@ kind. request API. - **Trust is the token prefix** (2026-09-29). Whoever the application's tokens let publish under the echo path may report, and no report is - authenticated beyond that. How far one viewer may move the encoder is - application-specific (a simulcast ladder suffers less from one viewer than - a single rendition), so the encoder's built-in step-down policy takes a - tunable config that the application can adjust or disable. + authenticated beyond that. - **Feedback track: one snapshot**, `{ transport, renditions: { : echo::Track } }`, so the publisher looks up its own renditions directly. - **One type per role, shared across kinds.** @@ -97,22 +93,9 @@ kind. `doc/concept/stats.md` gains a media section beside the relay's, and `drafts/draft-lcurley-moq-hang.md` specs the wire. -Open, to settle before [encoder feedback](/quest/m2/stats-encoder-feedback.md) -starts: - -- **Referenced-rendition feedback.** A derivative catalog (a `moq-transcode` - passthrough) collects feedback for a source rendition it lists, but owns - no encoder for it, and the source encoder reads only its own catalog's - prefix. Candidates: the derivative forwards those rows to the source's - echo path, or the source encoder also reads catalogs that reference it, - or referenced renditions stay report-only. -- **Shared echo prefixes.** Two catalogs can resolve their echo paths to one - prefix (`../viewers` from `room/a/live` and `room/b/live`). Then a viewer - using one name for both closes one `.echo` with the other, and each - publisher reads the other's reports under a shared alias. Candidates: - require each catalog's echo prefix to be its own, as application policy - like the token rights, or carry the catalog's broadcast in the snapshot - and ignore reports for another. +Decided in the 2026-09-30 audit: a Rust encoder adapting its bitrate to viewer +feedback moved to [encoder feedback](/quest/m2/stats-encoder-feedback.md) (m2), +along with its open questions. This line only publishes and reads the reports. ## Required @@ -129,3 +112,5 @@ starts: - [QoS](/quest/m1/qos/README.md) - the relay's delivery counters, the other half of a health verdict +- [Encoder feedback](/quest/m2/stats-encoder-feedback.md) - a Rust encoder + adapts its bitrate to what its viewers report diff --git a/quest/m1/subscribe-drop.md b/quest/m1/subscribe-drop.md index ecd671811c..a74d3596df 100644 --- a/quest/m1/subscribe-drop.md +++ b/quest/m1/subscribe-drop.md @@ -47,6 +47,17 @@ Regression tests: a publisher that expires a group, skips a sequence, and resets a stream before its header; on each version the subscriber settles without waiting out the grace. +Add the lite-07 drop case to the tail interop harness from +[track tail interop](/quest/m1/track-tail-interop.md): Rust and JS +subscribers both settle on SUBSCRIBE_DROP through the relay, so a group the +publisher skipped or never opened ends the track without waiting out the +grace. Decided in the 2026-09-30 audit: the case moved here so the basic +tail interop could land first. + +PR #4455 (`quest/m1/rs2ts/lite-leading-ones`) also edits the lite-07 wire +(varints) and no quest tracks it; coordinate the draft's lite-07 changelog +with it. + ## Related -- [Track tail interop](/quest/m1/track-tail-interop.md) - the Rust-JS proof of the lite-07 drop case +- [Track tail interop](/quest/m1/track-tail-interop.md) - the Rust-JS tail harness the lite-07 drop case extends diff --git a/quest/m1/test-flakes-2.md b/quest/m1/test-flakes-2.md deleted file mode 100644 index a5941853f9..0000000000 --- a/quest/m1/test-flakes-2.md +++ /dev/null @@ -1,90 +0,0 @@ -# [S] More tests hold up under load - -## Goal - -A second round after [#4286](https://github.com/moq-dev/moq/pull/4286): -tests that pass alone but have failed under a loaded `just check` pass -reliably, each fixed at its cause, never by raising a timeout or adding a -retry. - -- moq-cli `fetch::tests::a_frame_read_times_out` asserts on a 500 ms wall - deadline ([#4084](https://github.com/moq-dev/moq/pull/4084)). The cause - (#4431): `rs/moq-cli/src/fetch.rs` wraps the whole run in one - `timeout_at(deadline, ...)`, so connect, TLS, announce, and subscribe share - the budget with the read, and under load setup alone can spend it. -- moq-cli `complete::tests::a_stage_broadcast_picks_the_catalog_to_read` - ([#4084](https://github.com/moq-dev/moq/pull/4084)), - `the_catalog_format_on_the_line_is_honored` - ([#4089](https://github.com/moq-dev/moq/pull/4089)), and - `a_relay_on_the_line_answers_broadcast`: both of the latter pair came back - empty after the fixed 1.5 s `CEILING` on a loaded runner - ([#4404](https://github.com/moq-dev/moq/pull/4404)). -- moq-net `model::group::test::drop_unfinished_warns` counts WARNs through a - global tracing capture, so another test's WARN, or a missed one, changes - the count ([#4104](https://github.com/moq-dev/moq/pull/4104)). The - `model::track` test of the same name uses the same helper. -- moq-tokio `broadcast_race_quic_wins` binds TCP `:0` and then UDP on the - same number, which nothing reserves: the collision - [#4084](https://github.com/moq-dev/moq/pull/4084) removed from its sibling - after [#4055](https://github.com/moq-dev/moq/pull/4055) papered over it with - a retry. -- moq-tokio - `subscription_end_integrity::a_subscription_cut_by_the_publisher_disconnecting_does_not_end_clean` - ends `Ok(None)` with 10 of 20 frames in 3 of 8 full-suite runs on a clean - tree, and passes alone (#4332). -- `just test media` late join failed once after - [#4181](https://github.com/moq-dev/moq/pull/4181): "joined at frame 111, 16 - frames behind 127", against a budget of one GOP (15). -- js/publish audio encoder test "a rendition trailing the broadcast's - earliest advertises delay" reads the real clock, so it fails when its file - runs alone (timestamps go negative early in the process) (#4414). Mock time. -- moq-mux `container::ts::export_test::debounce_opens_without_a_media_clock` - died with SIGTERM once in a combined run. It is marked - `start_paused = true` but sleeps 1.2 s of real time, because the debounce - reads `crate::Clock`, which uses `std::time::Instant`, so the paused tokio - clock never reaches it. -- moq-tokio websocket `fixed_addresses_keep_tls_name_and_request_host` and - `ipv6_literal_fixed_addresses` pause the clock over a real TLS dial on - loopback. They pass today, but once a timeout lands on that path, the paused - clock can fire it before loopback delivers, the race - [#4527](https://github.com/moq-dev/moq/pull/4527) removed from the auth - outage tests. - -## Plan - -- Timing tests: prefer a paused clock over wall time (`moq-cli`'s - subscribe tests already use `#[tokio::test(start_paused = true)]`), or - assert on an event instead of a deadline. If a test is slow under load - because the code under test is slow, fix that. -- Fetch timeout: test on a paused clock (maintainer decision, 2026-09-28). - The fixture runs real sockets against an in-process relay, where a paused - clock fires QUIC timers while packets are in flight, so first make the - timers mockable: run the fixture over an in-memory transport, or drive - noq's timers from the test clock, whichever is smaller. Keep the one - absolute 30 s deadline, matching the relay's `/fetch`; on a paused clock - setup costs no time, so it can't spend the read's budget. -- moq-mux debounce: let the test drive `crate::Clock`'s time (a tokio - `Instant` under test, or an injected source) so the window passes on the - paused clock, and drop the real sleep. -- WARN counting: capture per test (a scoped subscriber or a filter on the - test's own span) instead of a process-global count. -- The race test shares one port only so both transports sit behind one URL. - Separate `:0` ports fix the bind collision but not the race itself: with - `websocket.delay = 0` either arm can legitimately win under load. Make the - order deterministic the way #4084 did, holding the WebSocket arm until QUIC - has connected, rather than asserting on a real race; no retry. #4084's follow-ups - (`tests/reconnect.rs` `spawn_server`, `tests/worker.rs` `free_udp_port`) - are the same probe-and-rebind pattern; fix them here if cheap. -- Media late join: first decide whether 16 frames is a real regression (the - player joining at the previous GOP's keyframe) or an off-by-one in how the - fixture samples the live edge. Fix whichever it is; don't widen the - budget without a reason. -- Prove it by running `just check --all` several times on a loaded machine, - as the first round did. - -Public API: none. Wire: none. - -## Related - -- [Archive enrollment](/quest/m1/archive/enrollment-flake.md) - the same - kind of flake on the archive line, where its test lives diff --git a/quest/m1/test-flakes-2/README.md b/quest/m1/test-flakes-2/README.md new file mode 100644 index 0000000000..c48c60452b --- /dev/null +++ b/quest/m1/test-flakes-2/README.md @@ -0,0 +1,46 @@ +# More tests hold up under load + +## Goal + +A second round after [#4286](https://github.com/moq-dev/moq/pull/4286): +tests that pass alone but have failed under a loaded `just check` pass +reliably, each fixed at its cause, never by raising a timeout or adding a +retry. + +## Plan + +Decided in the 2026-09-30 audit: the round held independent flakes in one +quest, so it split into one child per flake, grouping only those that share +a fixture. Each child lands on its own. + +Rules every child keeps: + +- Prefer a paused clock over wall time (`moq-cli`'s subscribe tests already + use `#[tokio::test(start_paused = true)]`), or assert on an event instead + of a deadline. If a test is slow under load because the code under test is + slow, fix that. +- A paused clock auto-advances while the runtime idles, so it can fire a + timer before a real socket delivers. Where real sockets fight the paused + clock, make the timers mockable or move the test off real sockets. + +This README's own work, after the children: run `just check --all` several +times on a loaded machine, as the first round did. + +Public API: none. Wire: none. + +## Required + +- [moq-cli tests on a paused clock](/quest/m1/test-flakes-2/cli-paused-clock.md) - the fetch timeout and completion tests stop racing wall-clock budgets +- [Subscription cut by disconnect](/quest/m1/test-flakes-2/subscription-cut.md) - a publisher disconnect never ends a subscription clean +- [Broadcast race](/quest/m1/test-flakes-2/broadcast-race.md) - the QUIC-wins race test binds no shared port and has a deterministic winner +- [Media late join](/quest/m1/test-flakes-2/media-late-join.md) - `just test media` late join stays within one GOP, or the regression is fixed +- [Shaper virtual time](/quest/m1/test-flakes-2/shaper-virtual-time.md) - `moq-shaper` tests judge seeded decisions on paused time, not on wall-clock delivery under load +- [Scoped WARN capture](/quest/m1/test-flakes-2/warn-capture.md) - the drop-unfinished tests count only their own WARNs +- [moq-mux debounce clock](/quest/m1/test-flakes-2/mux-debounce-clock.md) - the TS export debounce test advances on the paused clock +- [js/publish audio clock](/quest/m1/test-flakes-2/publish-audio-clock.md) - the audio encoder delay test runs on mock time +- [WebSocket paused TLS dial](/quest/m1/test-flakes-2/websocket-paused-tls.md) - the fixed-address WebSocket tests stop pausing the clock over a real dial + +## Related + +- [Archive enrollment](/quest/m1/archive/enrollment-flake.md) - the same + kind of flake on the archive line, where its test lives diff --git a/quest/m1/test-flakes-2/broadcast-race.md b/quest/m1/test-flakes-2/broadcast-race.md new file mode 100644 index 0000000000..36b0ddf185 --- /dev/null +++ b/quest/m1/test-flakes-2/broadcast-race.md @@ -0,0 +1,22 @@ +# [S] Broadcast race without a shared port + +## Goal + +moq-tokio `broadcast_race_quic_wins` binds no port it didn't reserve and +has a deterministic winner. + +## Plan + +It binds TCP `:0` and then UDP on the same number, which nothing reserves: +the collision [#4084](https://github.com/moq-dev/moq/pull/4084) removed from +its sibling after [#4055](https://github.com/moq-dev/moq/pull/4055) papered +over it with a retry. + +The test shares one port only so both transports sit behind one URL. +Separate `:0` ports fix the bind collision but not the race itself: with +`websocket.delay = 0` either arm can legitimately win under load. Make the +order deterministic the way #4084 did, holding the WebSocket arm until QUIC +has connected, rather than asserting on a real race; no retry. #4084's +follow-ups (`tests/reconnect.rs` `spawn_server`, `tests/worker.rs` +`free_udp_port`) are the same probe-and-rebind pattern; fix them here if +cheap. diff --git a/quest/m1/test-flakes-2/cli-paused-clock.md b/quest/m1/test-flakes-2/cli-paused-clock.md new file mode 100644 index 0000000000..f29e8ab11c --- /dev/null +++ b/quest/m1/test-flakes-2/cli-paused-clock.md @@ -0,0 +1,30 @@ +# [M] moq-cli tests on a paused clock + +## Goal + +The moq-cli fetch and completion tests pass under any host load: none of +them races a wall-clock budget against connect, TLS, announce, and +subscribe. + +## Plan + +- `fetch::tests::a_frame_read_times_out` asserts on a 500 ms wall deadline + ([#4084](https://github.com/moq-dev/moq/pull/4084)). + `rs/moq-cli/src/fetch.rs` wraps the whole run in one + `timeout_at(deadline, ...)`, so setup shares the budget with the read, and + under load setup alone can spend it. +- `complete::tests::a_stage_broadcast_picks_the_catalog_to_read` + ([#4084](https://github.com/moq-dev/moq/pull/4084)), + `the_catalog_format_on_the_line_is_honored` + ([#4089](https://github.com/moq-dev/moq/pull/4089)), and + `a_relay_on_the_line_answers_broadcast`: the latter pair came back empty + after the fixed 1.5 s `CEILING` on a loaded runner + ([#4404](https://github.com/moq-dev/moq/pull/4404)). + +Test on a paused clock (maintainer decision, 2026-09-28). The fixture runs +real sockets against an in-process relay, where a paused clock fires QUIC +timers while packets are in flight, so first make the timers mockable: run +the fixture over an in-memory transport, or drive noq's timers from the test +clock, whichever is smaller. Keep fetch's one absolute 30 s deadline, +matching the relay's `/fetch`; on a paused clock setup costs no time, so it +can't spend the read's budget. The completion tests reuse the same fixture. diff --git a/quest/m1/test-flakes-2/media-late-join.md b/quest/m1/test-flakes-2/media-late-join.md new file mode 100644 index 0000000000..c795da647c --- /dev/null +++ b/quest/m1/test-flakes-2/media-late-join.md @@ -0,0 +1,15 @@ +# [S] Media late join within one GOP + +## Goal + +`just test media` late join holds its one-GOP budget under load, or the +regression behind it is fixed. + +## Plan + +It failed once after [#4181](https://github.com/moq-dev/moq/pull/4181): +"joined at frame 111, 16 frames behind 127", against a budget of one GOP +(15). First decide whether 16 frames is a real regression (the player +joining at the previous GOP's keyframe) or an off-by-one in how the fixture +samples the live edge. Fix whichever it is; don't widen the budget without a +reason. diff --git a/quest/m1/test-flakes-2/mux-debounce-clock.md b/quest/m1/test-flakes-2/mux-debounce-clock.md new file mode 100644 index 0000000000..e29f84934b --- /dev/null +++ b/quest/m1/test-flakes-2/mux-debounce-clock.md @@ -0,0 +1,15 @@ +# [XS] moq-mux debounce test on the paused clock + +## Goal + +moq-mux `container::ts::export_test::debounce_opens_without_a_media_clock` +advances on the paused clock and sleeps no real time. + +## Plan + +It died with SIGTERM once in a combined run. It is marked +`start_paused = true` but sleeps 1.2 s of real time, because the debounce +reads `crate::Clock`, which uses `std::time::Instant`, so the paused tokio +clock never reaches it. Let the test drive `crate::Clock`'s time (a tokio +`Instant` under test, or an injected source) so the window passes on the +paused clock, and drop the real sleep. diff --git a/quest/m1/test-flakes-2/publish-audio-clock.md b/quest/m1/test-flakes-2/publish-audio-clock.md new file mode 100644 index 0000000000..ccc4becaad --- /dev/null +++ b/quest/m1/test-flakes-2/publish-audio-clock.md @@ -0,0 +1,11 @@ +# [XS] js/publish audio delay test on mock time + +## Goal + +The js/publish audio encoder test "a rendition trailing the broadcast's +earliest advertises delay" passes when its file runs alone. + +## Plan + +It reads the real clock, so timestamps go negative early in the process and +it fails when run alone (#4414). Run it on mock time. diff --git a/quest/m1/shaper-virtual-time.md b/quest/m1/test-flakes-2/shaper-virtual-time.md similarity index 100% rename from quest/m1/shaper-virtual-time.md rename to quest/m1/test-flakes-2/shaper-virtual-time.md diff --git a/quest/m1/test-flakes-2/subscription-cut.md b/quest/m1/test-flakes-2/subscription-cut.md new file mode 100644 index 0000000000..8819c67ed9 --- /dev/null +++ b/quest/m1/test-flakes-2/subscription-cut.md @@ -0,0 +1,14 @@ +# [S] Subscription cut by publisher disconnect + +## Goal + +moq-tokio +`subscription_end_integrity::a_subscription_cut_by_the_publisher_disconnecting_does_not_end_clean` +passes in every full-suite run. + +## Plan + +It ends `Ok(None)` with 10 of 20 frames in 3 of 8 full-suite runs on a clean +tree, and passes alone (#4332). A clean end after a publisher disconnect is a +real bug if the code can produce it, not only a test race: reproduce under +load first, find which path reports the cut as a clean end, and fix it there. diff --git a/quest/m1/test-flakes-2/warn-capture.md b/quest/m1/test-flakes-2/warn-capture.md new file mode 100644 index 0000000000..d6f928e0c4 --- /dev/null +++ b/quest/m1/test-flakes-2/warn-capture.md @@ -0,0 +1,14 @@ +# [XS] Scoped WARN capture + +## Goal + +moq-net `model::group::test::drop_unfinished_warns` and the `model::track` +test of the same name count only the WARNs their own code emits. + +## Plan + +Both count WARNs through a global tracing capture, so another test's WARN, +or a missed one, changes the count +([#4104](https://github.com/moq-dev/moq/pull/4104)). Capture per test, with a +scoped subscriber or a filter on the test's own span, instead of a +process-global count. diff --git a/quest/m1/test-flakes-2/websocket-paused-tls.md b/quest/m1/test-flakes-2/websocket-paused-tls.md new file mode 100644 index 0000000000..d92b724882 --- /dev/null +++ b/quest/m1/test-flakes-2/websocket-paused-tls.md @@ -0,0 +1,14 @@ +# [XS] WebSocket fixed-address tests off the paused clock + +## Goal + +moq-tokio websocket `fixed_addresses_keep_tls_name_and_request_host` and +`ipv6_literal_fixed_addresses` no longer pause the clock over a real TLS +dial. + +## Plan + +They pass today, but they pause the clock over a real TLS dial on loopback. +Once a timeout lands on that path, the paused clock can fire it before +loopback delivers, the race [#4527](https://github.com/moq-dev/moq/pull/4527) +removed from the auth outage tests. Remove it the same way. diff --git a/quest/m1/track-priority-scope.md b/quest/m1/track-priority-scope.md index 4e4552e432..5e81a1a521 100644 --- a/quest/m1/track-priority-scope.md +++ b/quest/m1/track-priority-scope.md @@ -57,6 +57,11 @@ Direction to settle in the draft first, then the code: or narrow the goal to subscription delivery. - Keep the current ranking: track priority, then subscription, then newest group; do not reintroduce a group-order direction knob. +- Decide whether the publisher's `track::Info::priority` breaks a tie + between equal subscriber priorities in `Priority::cmp` + (`rs/moq-net/src/lite/priority.rs:48`). The + [ladder controller](/quest/m2/ladder/controller.md), now in m2, wants that + tiebreak; this quest owns the answer so the controller only consumes it. - A per-session cap on distinct ranks is a scheduling detail; whatever replaces the 255-entry sort must stay O(log n) per group under chat-shaped churn. @@ -77,3 +82,5 @@ change. - [Starvation](/quest/m1/qos/starvation.md) - the relay-side signal that shows a starved subscription +- [Signed priority](/quest/m2/signed-priority.md) - changes the priority type, + not which streams it competes with diff --git a/quest/m1/track-tail-interop.md b/quest/m1/track-tail-interop.md index 7e664668ff..e31403931b 100644 --- a/quest/m1/track-tail-interop.md +++ b/quest/m1/track-tail-interop.md @@ -24,23 +24,17 @@ What stood in the way when the Rust half landed: first frame. It needs a mode that reads a track to its end and reports how it ended and which groups it saw. -Also cover the drop case: +The harness exposed a relay start-floor defect: when a newer group arrives +first, earlier in-flight groups can be lost. #4387 fixed it (merged 09-28). -- On moq-lite-07 add a drop case: both subscribers settle on SUBSCRIBE_DROP, - so a group the publisher skipped or never opened ends the track without - waiting out the grace. lite-07 replaces the SUBSCRIBE_END stream count - (#4224) with it. -- The harness exposed a relay start-floor defect: when a newer group arrives - first, earlier in-flight groups can be lost. #4387 fixes it, so the drop - proof waits on it. +Decided in the 2026-09-30 audit: the lite-07 drop case moved into +[SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md)'s tests, so the basic Rust and +JS tail interop lands now instead of waiting on that [L] quest. QUIC on localhost rarely reorders, so this is a smoke check that the end is delivered and clean. The ordering race itself stays in the unit tests. -## Required - -- [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - publishers name every group they won't deliver, which the lite-07 case checks - ## Related +- [SUBSCRIBE_DROP](/quest/m1/subscribe-drop.md) - owns the lite-07 drop case on top of this harness - [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - keeps a reset stream's header, so the reset acts as a one-group drop diff --git a/quest/m1/transport-upgrade/README.md b/quest/m1/transport-upgrade/README.md index 094817c6e0..25a197bed5 100644 --- a/quest/m1/transport-upgrade/README.md +++ b/quest/m1/transport-upgrade/README.md @@ -45,7 +45,7 @@ Shared decisions: `moq-tokio`, `websocketWon` in `js/net`) forgets the URL: QUIC works on this network, so the head start comes back. Otherwise a network where WebSocket narrowly beats QUIC would open two connections on every reconnect. -- The old session gets `Goaway::same()` with the configured handover cap before +- The old session gets `Goaway::new()` with the configured handover cap before it enters draining. The relay refuses new requests on it from then on; the splice ends its subscriptions at the boundary. - One-shot `connect()` returns one session and never upgrades; every diff --git a/quest/m1/uring-ietf.md b/quest/m1/uring-ietf.md index 13cc9cfbb7..c860ec359d 100644 --- a/quest/m1/uring-ietf.md +++ b/quest/m1/uring-ietf.md @@ -28,3 +28,8 @@ gap is in the worker's accept path, not in moq-net. Additive, so it lands on main. moq.pro's fleet deploy of the ring requires the release carrying it. + +## Related + +- [Stream sessions](/quest/m2/uring-tcp/README.md) - the other protocol gap + on the ring, WebSocket and HTTP, deferred to m2 diff --git a/quest/m1/watch-worker.md b/quest/m1/watch-worker.md index 21501340e4..527a11c747 100644 --- a/quest/m1/watch-worker.md +++ b/quest/m1/watch-worker.md @@ -31,6 +31,7 @@ Gate on the plan's jank harness and N-player sweep, both nightly. ## Required - [Plan: watch worker](/quest/m1/plan-watch-worker.md) - picks the worker model and rewrites this quest +- [A/V clock](/quest/m1/av-clock.md) - reshapes `Sync` and the worklet playhead, so the move to the worker happens once ## Related diff --git a/quest/m1/wt-close-upstream.md b/quest/m1/wt-close-upstream.md index 2b4d76f176..dd87c3a8e8 100644 --- a/quest/m1/wt-close-upstream.md +++ b/quest/m1/wt-close-upstream.md @@ -34,3 +34,12 @@ Decided 2026-09-29: fix it at the source and remove the timeout workaround. moq-tokio. Public API: none. Wire: none. + +Decided in the 2026-09-30 audit: the UnknownSession log flood quest merged +here, since the same noq 1.3.3 release (moq-dev/noq#21) carries its fix. +`decode_uni` and `decode_bi` mapped a stream reset before its WebTransport +header to `UnknownSession`, flooding relay logs with WARNs; the fork now +keeps the read's cause and logs a reset at debug. Remaining steps: release +`web-transport-moq` 1.3.3, bump the pin, delete `CLOSE_LINGER` +(`rs/moq-tokio/src/transport.rs:24`), and confirm on a moq.pro relay that the +flood stops. diff --git a/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md b/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md index 384a5f33b7..532a8543e6 100644 --- a/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md +++ b/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md @@ -1,29 +1,29 @@ -# [L] Complete NVIDIA Main10 and AV1 encoding support +# [M] Complete NVIDIA Main10 encoding support ## Goal -Implement and verify the remaining 10-bit HEVC and AV1 encoding work tracked -in [#2147](https://github.com/moq-dev/moq/issues/2147). NVDEC AV1 decoding and +Implement and verify the remaining 10-bit HEVC encoding work tracked in +[#2147](https://github.com/moq-dev/moq/issues/2147). NVDEC AV1 decoding and catalog AV1 types already exist; do not reimplement them. ## Plan +Decided in the 2026-09-30 audit: AV1 encoding split out to +[NVENC AV1](/quest/m3/nvenc-av1.md), because it needs an Ada GPU and the only +GPU CI host is an RTX 3070 Ti. Main10 stays here since that host can verify it. + Extend the settled frame and NVENC contracts with Main10 surfaces, profile selection, and accurate codec metadata. Audit byte pitch, plane layout, CPU download, and P016 input/output together; a codec enum alone does not establish 10-bit support. Preserve color metadata and refuse unsupported conversions. Do not imply that OpenH264 can decode HEVC or tonemap HDR. -Add AV1 encoding only where the queried NVIDIA device/driver supports it. -Preserve OBU framing and accurate catalog configuration through transcode. Use existing extensible codec enums; no replacement of the 0.1 core API is planned. Keep the NVIDIA backend optional and loaded at runtime. -Split Main10 and AV1 implementation into independent PRs if hardware or review -scope warrants it. Validate decoded pixels, bit depth, profile, framing, -resource lifetime, drain, and refusal on unsupported devices. Wire fixtures -and contract tests into CI, and record actual hardware execution separately. -Lack of suitable hardware leaves that implementation unverified, not complete. +Validate decoded pixels, bit depth, profile, resource lifetime, drain, and +refusal on unsupported devices. Wire fixtures and contract tests into CI, and +run the hardware tests on the [GPU CI](/quest/m1/gpu-ci.md) host. Public API: additive capabilities on the extension points settled on main. Wire: existing codec signaling, with cross-language fixtures for any metadata change. @@ -34,4 +34,6 @@ codec signaling, with cross-language fixtures for any metadata change. ## Related +- [GPU CI](/quest/m1/gpu-ci.md) - the RTX 3070 Ti host that verifies Main10 +- [NVENC AV1](/quest/m3/nvenc-av1.md) - the AV1 half of #2147, hardware-gated - [Codec coverage study](/quest/m2/video-codec-coverage.md) - measure optional software and other native backends separately diff --git a/quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md b/quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md index eacb636ae1..0ddef060e6 100644 --- a/quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md +++ b/quest/m2/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md @@ -9,6 +9,8 @@ exporter understands. Server-side ad insertion is a separate future quest. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer for timed metadata. + Use the shared event contract [emsg](/quest/m2/emsg.md) settles. Deliver cues immediately, including when splice_time is in the future; consumers need advance notification. Metadata group sequences are independent of media GOPs. @@ -43,3 +45,7 @@ be in it the day it ships. Cross-package sync: `rs/hang`, `js/hang`, ## Closes - [#2279](https://github.com/moq-dev/moq/issues/2279) - close this issue when the quest finishes + +## Related + +- [ID3 catalog section](/quest/m2/id3.md) - the other typed timed-metadata section, same rule diff --git a/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md b/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md index 9b6eaad34f..d479c5efca 100644 --- a/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md +++ b/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md @@ -4,7 +4,7 @@ The Linux zero-copy spine from [#2819](https://github.com/moq-dev/moq/issues/2819), `PipeWire DMA-BUF -> Surface::DmaBuf -> Vulkan import -> render shader`, is -proven on real hardware, and a V4L2 camera feeds `Surface::DmaBuf` too. +proven on real hardware. ## Plan @@ -26,9 +26,11 @@ What remains: driver will not import, and re-tiles nothing. A VA-API VPP re-tile is only worth adding if a measured capture source lands on such a modifier; record the modifiers seen and decide. -- V4L2 capture still converts to I420 on the CPU. Export its buffers with - `VIDIOC_EXPBUF` (the ioctl is in `moq-v4l`, unused) as a `Surface::DmaBuf` - so a camera reaches VA-API or NVENC without a copy. + +Decided in the 2026-09-30 audit: V4L2 `VIDIOC_EXPBUF` export is dropped. +V4L2 capture only takes YUYV and MJPEG (`rs/moq-video/src/capture/v4l2.rs`), +both of which need a CPU conversion or decode anyway, so exporting the buffer +saves no copy. Revisit only if NV12 camera capture lands. Refs #2481, #1837. diff --git a/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md b/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md index d9ae6fa649..c6f9e78336 100644 --- a/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md +++ b/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md @@ -31,6 +31,14 @@ The reason it is not a small change: `poll_open_uni` would have to hold a half-o Worth confirming the trade too: making `open` block on credit moves the backpressure earlier, which is more correct but changes when a caller learns about it. +Decided in the 2026-09-30 audit: moved to m2. No in-tree caller hits the +wart (moq-net always pairs `finish()` with `poll_closed`), and io_uring +ships in no package. + +## Required + +- [Open contract](/quest/m2/uring-open-contract.md) - settle concurrent ownership and backpressure before implementation + ## Closes - [#3129](https://github.com/moq-dev/moq/issues/3129) - close this issue when the quest finishes diff --git a/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md b/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md index 9dd02084d0..3bf50216a6 100644 --- a/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md +++ b/quest/m2/3200-moq-uring-batch-completion-wakeups-with-min-timeout.md @@ -35,6 +35,9 @@ The kernel returns when `N` CQEs arrive, when `t` expires after partial progress Benchmark chat, 1:1 video, and fanout workloads with `N = 1/4/8/16` and `t = 0/5/10/20 us`. Record CQEs per wake, enters per second, CPU per message, p50, p99, and p999 latency. Pick no production default until the latency budget and CPU win are both demonstrated. +Decided in the 2026-09-30 audit: moved to m2. It trades latency for CPU, +the win is unmeasured on noq, and io_uring ships in no package. + ## Closes - [#3200](https://github.com/moq-dev/moq/issues/3200) - close this issue when the quest finishes diff --git a/quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md b/quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md index bb9a3d65b7..67f44fde70 100644 --- a/quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md +++ b/quest/m2/3202-moq-uring-use-fixed-file-slots-for-worker-udp-sockets.md @@ -30,6 +30,9 @@ Registered files let SQEs address a stable table slot with `types::Fixed`. This Benchmark steady-state send/receive traffic and high socket-churn workloads. Record CPU, cycles, instructions, throughput, and socket lifetime cost. Keep the implementation only if the hot-path win justifies the slot-lifecycle complexity. +Decided in the 2026-09-30 audit: moved to m2. The hot-path win is +unmeasured on noq, and io_uring ships in no package. + ## Closes - [#3202](https://github.com/moq-dev/moq/issues/3202) - close this issue when the quest finishes diff --git a/quest/m2/README.md b/quest/m2/README.md index b915f3276a..7ca81c250e 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -10,13 +10,13 @@ Nothing here blocks a release. Promote a quest into [m1](/quest/m1/README.md) when it joins the next wave, including planning work whose decisions are worth settling now; deferral does not abandon a feature. A study may end with a measured no-go. Work gated on hardware, a -partner, or a provider waits in [m3](/quest/m3/README.md); work waiting on an -upstream release waits in [m4](/quest/m4/README.md). +partner, a consumer, or a provider waits in [m3](/quest/m3/README.md); work +waiting on an upstream release waits in [m4](/quest/m4/README.md). ## Required - [P2P](/quest/m2/p2p/README.md) - opted-in clients serve each other over data channels and iroh while the relay stays the rendezvous and the fallback, under application policy -- [One port](/quest/m2/one-port/README.md) - a relay speaks QUIC, STUN, WebRTC media, and SRT on one UDP port and HTTP, RTMP, and RTMPS on one TCP port +- [One port](/quest/m2/one-port/README.md) - a relay speaks QUIC and STUN on one UDP port and HTTP, RTMP, and RTMPS on one TCP port; WebRTC media is an embedder hook - [Ladder](/quest/m2/ladder/README.md) - a transcode ladder adapts to the uplink it publishes over, instead of encoding every live rung at its ceiling - [Processor](/quest/m2/processor/README.md) - a customer-run worker publishes an on-demand contribution under its own service prefix with scoped access - [Stream sessions](/quest/m2/uring-tcp/README.md) - serve WebSocket and HTTP from the io_uring workers, where io_uring pays off most @@ -60,16 +60,17 @@ upstream release waits in [m4](/quest/m4/README.md). reads its viewers' feedback and adapts its bitrate - [Text availability](/quest/m2/text-schema.md) - a text track publishes its own coverage index instead of copying the media timeline - [Closure counters](/quest/m2/closure-counters.md) - a departed node's return never regresses the closure counters a consumer already saw -- [Bench coverage](/quest/m2/bench-coverage.md) - Criterion targets for moq-mux containers, the hang catalog, moq-auth verification, and moq-pattern matching +- [Bench coverage](/quest/m2/bench-coverage.md) - Criterion targets for moq-pattern matching first, then the stats producer, moq-mux containers, the hang catalog, and moq-auth - [Signed priority](/quest/m2/signed-priority.md) - on dev, every API priority is an `i8` with 0 as the unset midpoint, and hang's built-ins sit above it - [AV1 metadata separation](/quest/m2/av1-metadata.md) - retain metadata OBUs inline while evaluating separate delivery -- [Catalog track identity](/quest/m2/catalog-tracks.md) - compare immutable track definitions with explicit catalog-to-group binding +- [Catalog track identity](/quest/m2/catalog-tracks.md) - a changed track configuration becomes a new track name or epoch, never a mutated definition +- [Catalog colour model](/quest/m2/color-catalog.md) - the catalog describes a rendition's colour and HDR properties once a renderer consumes them - [Archive recovery listing](/quest/m2/archive-recovery-listing.md) - a resumed DVR lists what changed since its checkpoint, not every stored group - [Relay io_uring packages](/quest/m2/relay-io-uring-package.md) - Linux relay packages ship io_uring once the ring is on par with tokio - [iOS capture](/quest/m2/mobile-capture-ios.md) - camera and screen capture if the mobile ownership decision selects Rust - [Android capture](/quest/m2/mobile-capture-android.md) - NDK/JNI capture using the existing codecs if mobile ownership selects Rust - [Mobile completion](/quest/m2/mobile-completion.md) - verify the selected native/mobile path before closing #700 -- [Opus implementation](/quest/m2/audio-opus-backend.md) - compare current codec quality, CPU, and optional build costs +- [Opus implementation](/quest/m2/audio-opus-backend.md) - compare Opus codec quality, CPU, build cost, and the loss recovery each backend offers - [Latency ledger](/quest/m2/latency-ledger.md) - a session reports where its end-to-end audio delay went, stage by stage - [JS LOC duration marker](/quest/m2/js-loc-duration-marker.md) - `@moq/loc`'s producer ends each video group with the empty duration frame, as moq-mux does - [Media Foundation decode](/quest/m2/audio-decode-mediafoundation.md) - Windows decodes HE-AAC, multichannel AAC, and what else the MFTs offer @@ -77,7 +78,7 @@ upstream release waits in [m4](/quest/m4/README.md). - [MediaCodec decode](/quest/m2/audio-decode-mediacodec.md) - Android decodes HE-AAC, multichannel AAC, and what else the device offers - [MediaCodec encode](/quest/m2/audio-encode-mediacodec.md) - Android encodes AAC-LC - [Video codec coverage](/quest/m2/video-codec-coverage.md) - prioritize remaining native AV1 and portable decoder gaps -- [#2147](/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md) - moq-video: 10-bit HEVC and AV1 support in the NVIDIA codec path +- [#2147](/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md) - moq-video: 10-bit HEVC in the NVIDIA codec path; AV1 is m3 - [NVENC buffer pool](/quest/m2/nvenc-pool.md) - NVENC reuses input and output buffers instead of allocating per frame, if a benchmark shows it wins - [NVENC held frames](/quest/m2/nvenc-held-frames.md) - moq-nvenc refuses or drives configurations whose frames the driver holds back - [Direct3D11 render import](/quest/m2/render-d3d11.md) - Windows presents without downloading every frame to system memory @@ -88,7 +89,7 @@ upstream release waits in [m4](/quest/m4/README.md). - [Binary delta stats](/quest/m2/stats-delta.md) - an on-demand varint delta flavor of every stats track, if relay encode CPU still matters after the JSON fixes - [#3115](/quest/m2/3115-moqsink-the-publication-has-no-generation-so-a-flush.md) - moqsink: a flushing restart after EOS opens a new publication generation - [Multipath spike](/quest/m2/multipath-spike.md) - whether bonded contribution over multipath QUIC is worth building, given it needs noq on both ends -- [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - whether bounded drain credit avoids ProbeRTT deadline interference +- [BBR media study](/quest/m2/quic-bbr-natural-drain.md) - whether bounded drain credit avoids ProbeRTT deadline interference, and where our BBR differs from Google's - [Discover media headroom](/quest/m2/quic-probe.md) - test useful-media pacing before adding redundant probe traffic - [L4S on the backbone](/quest/m2/quic-ecn.md) - an ECT(1) option in the fork, an `ecn` config knob, and a dualpi2 measurement - [Careful resume on reconnect](/quest/m2/quic-careful-resume.md) - a redial starts at the previous connection's rate @@ -106,9 +107,9 @@ upstream release waits in [m4](/quest/m4/README.md). - [Linux capture parity](/quest/m2/capture-linux.md) - Wayland window/system-audio capture with explicit display-selection and app-capture limits - [Audio capture time](/quest/m2/audio-capture-time.md) - native audio stamps a buffer's capture instant, not when the driver reads it - [X11 capture transport](/quest/m2/x11-capture-shm.md) - move X11 capture to shared memory and RandR events instead of a per-frame socket copy -- [Send batching](/quest/m2/quic-egress-profile.md) - whether sendmmsg across connections pays on the tokio path -- [SEI separation](/quest/m2/sei.md) - retain inline SEI until measured savings or a metadata-only consumer justify a split -- [Compressed tracks](/quest/m2/flate.md) - the hand-written binding wrappers expose flate tracks +- [Egress profile](/quest/m2/quic-egress-profile.md) - measure relay send-path syscalls, pacing bursts, and allocations before optimizing any of them +- [SEI separation study](/quest/m2/sei.md) - measure whether separating SEI saves enough, or has a metadata-only consumer, to justify a split +- [Compressed tracks](/quest/m2/flate.md) - moq-ffi and every wrapper expose flate tracks through a `flate` namespace like `json` - [Announcement shapes](/quest/m2/announce-shapes.md) - moq-lite announcements and interests carry prefix, exact, suffix, or prefix+suffix shapes that survive relay hops, benchmarked over the announce table -- [MSFTS convergence](/quest/m2/msfts-convergence.md) - the demultiplexed TS lane maps onto MSFTS ES-level carriage once msfts#33 settles the payload unit -- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - H.265 encode and decode, and pre-generated bindings that remove the libclang build dependency, gated on a moq-dev/vaapi release +- [MSFTS convergence](/quest/m2/msfts-convergence.md) - the demultiplexed TS lane converges on MSFTS where the two still differ: program tables and the ES payload unit +- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - H.265 encode and decode, pre-generated bindings, and pooled resize surfaces, including the moq-dev/vaapi release that carries them diff --git a/quest/m2/announce-shapes.md b/quest/m2/announce-shapes.md index 2bae9d7648..db1263cce2 100644 --- a/quest/m2/announce-shapes.md +++ b/quest/m2/announce-shapes.md @@ -26,9 +26,11 @@ Decided 2026-09-29 (quest-plan interview): announcements (PUBLISH_NAMESPACE and NAMESPACE are prefix routes), so a session negotiated as IETF never carries a shape and there is no draft to converge with. -- The quest stays in m3, grown from the suffix-announce quest rather than a - new m1 quest, because that quest already holds the benchmark-first plan and - the version-gated wire approach both shapes need. +- The quest grew from the suffix-announce quest rather than a new m1 quest, + because that quest already holds the benchmark-first plan and the + version-gated wire approach both shapes need. +- Moved from m3 to m2 in the 2026-09-30 audit: it has no external gate, and + the exact-scope leak is a real bug. - The old gate ("a deployment needs a suffix claim a service prefix cannot express") is removed: the exact-scope leak is reason enough. @@ -52,8 +54,10 @@ cases as vectors both languages test. Only these four shapes go on the wire. A richer interest pattern (`pid/*/chat`) stays a consume-side filter over the widest shape that covers -it, as [path patterns](/quest/m1/path-patterns.md) decided for every pattern -before this quest. +it. Path patterns already shipped for that filtering and for token claims +(`moq-pattern`, `@moq/pattern`, +[#3746](https://github.com/moq-dev/moq/pull/3746), and the +[auth line](/quest/m1/auth/README.md)). ### Model @@ -103,3 +107,4 @@ today, and this quest extends them to the new shapes. Token patterns ## Related - [Wildcard](/quest/m0/wildcard/README.md) - prefix-only advertisements and the service-prefix layout this extends +- [Cluster routing](/quest/m1/cluster-routing/README.md) - forwards announcements between relays, which must keep their shape diff --git a/quest/m2/archive-browser.md b/quest/m2/archive-browser.md index 520a5cb09e..1a6d5bc935 100644 --- a/quest/m2/archive-browser.md +++ b/quest/m2/archive-browser.md @@ -31,8 +31,10 @@ the supported drafts without duplicating transport dispatch or codecs here. Ship the contract in the `@moq/*` packages. A dashboard browser-to-HLS proof remains downstream (moq.pro) work. +Deferred to m2 in the 2026-09-30 audit: no named consumer; the native archive +lands first. + ## Required - [JavaScript FETCH](/quest/m1/js-fetch.md) - generic on-demand group serving and IETF FETCH support - -- [Recording writer](/quest/m1/archive/writer.md) +- [Archive](/quest/m1/archive/README.md) - the native writer, reader, and DVR contract this ports diff --git a/quest/m2/archive-paced-replay.md b/quest/m2/archive-paced-replay.md index c17cd658e6..9286a61016 100644 --- a/quest/m2/archive-paced-replay.md +++ b/quest/m2/archive-paced-replay.md @@ -25,3 +25,9 @@ who joins late joins mid-replay. - Test with paused time: record, replay paced, and assert a plain subscriber gets every group in order at media pace, that a late subscriber starts at the current group, and that two tracks stay aligned. +- Deferred to m2 in the 2026-09-30 audit: no named consumer; FETCH replay + already serves DVR and HLS. + +## Required + +- [Recording reader](/quest/m1/archive/reader.md) - the FETCH reader this adds live publishing to diff --git a/quest/m2/audio-decode-mediacodec.md b/quest/m2/audio-decode-mediacodec.md index 936bc13a32..3549847226 100644 --- a/quest/m2/audio-decode-mediacodec.md +++ b/quest/m2/audio-decode-mediacodec.md @@ -25,6 +25,7 @@ behind a new optional audio `mediacodec` feature and the decode seam, on `target ## Required +- [Mobile ownership](/quest/m1/mobile-ownership.md) - if Kotlin owns platform codecs, this backend is moot - [Decode seam](/quest/m1/audio-codecs/decode-backend.md) - the candidate order this backend joins - [Layout](/quest/m1/audio-codecs/layout.md) - what a multichannel frame is delivered as diff --git a/quest/m2/audio-decode-mediafoundation.md b/quest/m2/audio-decode-mediafoundation.md index d4edf77dfe..2fb647fff1 100644 --- a/quest/m2/audio-decode-mediafoundation.md +++ b/quest/m2/audio-decode-mediafoundation.md @@ -26,3 +26,7 @@ ones. Behind the decode seam as the first candidate on `target_os = - [Decode seam](/quest/m1/audio-codecs/decode-backend.md) - the candidate order this backend joins - [Layout](/quest/m1/audio-codecs/layout.md) - what a multichannel frame is delivered as + +## Related + +- [Windows decoded frames](/quest/m2/obs-decode-windows.md) - the OBS Windows line this feeds diff --git a/quest/m2/audio-encode-mediacodec.md b/quest/m2/audio-encode-mediacodec.md index 7bb26674ea..5adc169241 100644 --- a/quest/m2/audio-encode-mediacodec.md +++ b/quest/m2/audio-encode-mediacodec.md @@ -20,6 +20,7 @@ behind the `mediacodec` feature and the encode seam. ## Required +- [Mobile ownership](/quest/m1/mobile-ownership.md) - if Kotlin owns platform codecs, this backend is moot - [Encode seam](/quest/m1/audio-codecs/encode-backend.md) - the candidate order this backend joins - [Layout](/quest/m1/audio-codecs/layout.md) - the input layout the encoder accepts - [MediaCodec decode](/quest/m2/audio-decode-mediacodec.md) - the round-trip regression decodes through it diff --git a/quest/m2/audio-opus-backend.md b/quest/m2/audio-opus-backend.md index a981f0e007..0a5ccf0e6f 100644 --- a/quest/m2/audio-opus-backend.md +++ b/quest/m2/audio-opus-backend.md @@ -1,9 +1,10 @@ -# [M] Compare current Opus implementations +# [M] Compare Opus implementations and loss recovery ## Goal -Determine whether an optional newer Opus backend improves quality or CPU enough -to justify its build and maintenance cost while retaining the simple Rust path. +Determine whether an optional newer Opus backend improves quality, CPU, or +loss recovery enough to justify its build and maintenance cost while retaining +the simple Rust path, and settle a tested loss-recovery policy. ## Plan @@ -18,6 +19,24 @@ CPU, and package/toolchain cost. Keep native compilation optional and use the existing private backend seam; do not add another public configuration surface just to expose one implementation's controls. A no-go is a valid outcome. +### Loss recovery + +Decided in the 2026-09-30 audit: the loss-recovery policy study folds in here, +because the options depend on the backend. DRED and deep PLC need libopus 1.5+, +while the current 1.3.1 port only offers in-band LBRR FEC and classic +concealment. + +Decide whether a concrete MoQ audio consumer benefits from FEC, DRED, or deep +PLC over concealment. The old FEC boolean supplied no expected-loss percentage +and our decoder never requested FEC recovery. Treat source loss, late-group +abandonment, and DTX as different cases. Define sequencing, expected loss, +playout lookahead, recovery versus concealment, and behavior when the next +packet is unavailable. Use deterministic dropped-packet fixtures to prove +redundancy is emitted and used, compare audible quality and delay, and name +the transport scenario where it helps. Any resulting policy is small and +additive on the extensible audio settings; do not reintroduce an enable flag +tested only by reading the codec's control value. + Retain fixtures and repeatable measurements in the existing CI/nightly audio harness. Public API and wire: no change for the study; validate compatibility before a separately scoped backend implementation. diff --git a/quest/m2/av1-metadata.md b/quest/m2/av1-metadata.md index 996a293290..afc4bec834 100644 --- a/quest/m2/av1-metadata.md +++ b/quest/m2/av1-metadata.md @@ -28,3 +28,9 @@ not. Preserve `metadata_type`, the payload bytes, and ordering within the temporal unit. Test HDR10+ T.35 metadata, timecode, an unknown metadata type, several OBUs in one temporal unit, and a byte-identical round trip. + +## Related + +- [SEI sidecars](/quest/m2/sei.md) - the H.26x contract this should + follow rather than duplicate +- [fMP4 emsg carriage](/quest/m2/emsg.md) - independent carriage of container metadata, not a prerequisite for codec extraction diff --git a/quest/m2/bench-coverage.md b/quest/m2/bench-coverage.md index 05d5b3077a..9e78d0cd71 100644 --- a/quest/m2/bench-coverage.md +++ b/quest/m2/bench-coverage.md @@ -1,13 +1,35 @@ -# [M] Benchmarks for containers, catalog, auth, and path matching +# [M] Benchmarks for patterns, stats, containers, catalog, and auth ## Goal Hot paths that have no benchmark get Criterion targets, so CI tracks them: -`moq-mux` container import and export per frame, `hang` catalog encode, decode, -and update, `moq-auth` token verification, and `moq-pattern` path matching. +`moq-pattern` path matching, the `moq-stats` producer per tick, `moq-mux` +container import and export per frame, `hang` catalog encode, decode, and +update, and `moq-auth` token verification. ## Plan +Deferred to m2 in the 2026-09-30 audit. The `moq-pattern` benchmark is the +one worth doing first, and could return to m1 as its own quest. + +- `moq-pattern`: matching swept over pattern count and path depth. Patterns + shipped as `rs/moq-pattern` and `js/pattern`, so the matcher gets one bench + here, not a second one elsewhere. +- `moq-stats` producer (folded in from the stats producer benchmark): measure + what one relay pays per stats tick to drain its registry and encode the + traffic tracks. #4299 keeps an idle path in every frame while the registry + holds its counters (about 430 B per idle path per plain frame), and nothing + measures that or the per-tick drain as held paths grow. Sweep held paths + (for example 100 to 50k) against tiers and the share of idle versus changed + paths. Report time and allocations per tick, and plain and compressed bytes + per frame the way `rs/moq-stats/benches/decode.rs` reports its table, + including the point where a plain frame nears its size cap. Drive the real + producer path (`process_slot`, the snapshot encoders) over a synthetic + `Registry`; a bench hook is fine if it stays out of the public API. This is + the moq-json snapshot encoder profile + [Binary delta stats](/quest/m2/stats-delta.md) gates on. `decode.rs` is not + in CI either; run both targets in the nightly benchmark smoke, as + `moq-net`'s are. - `moq-mux`: fMP4/CMAF and Annex-B import and export over checked-in or generated fixtures, with throughput in frames and bytes. Keep fixture generation out of the timed region. @@ -16,13 +38,13 @@ and update, `moq-auth` token verification, and `moq-pattern` path matching. - `moq-auth`: JWT verification per connection, swept over algorithm and claim size. The in-band token path gets its bench with [In-band token](/quest/m1/auth/token-in-band.md), not here. -- `moq-pattern`: matching swept over pattern count and path depth. Coordinate - with [Path patterns](/quest/m1/path-patterns.md) so the matcher gets one - bench, not two. Anything that fans out gets a sweep over both axes. Name each target after what it measures, so the CI comment reads without opening the file. +Public API: none. Wire: none. + ## Related - [Benchmark regressions in CI](/quest/m1/bench-ci.md) - tracks these targets on PRs and nightly +- [Binary delta stats](/quest/m2/stats-delta.md) - its gate needs the stats encode baseline this measures diff --git a/quest/m2/browser-media-qa-engines.md b/quest/m2/browser-media-qa-engines.md index fce4bce8af..2cf1adce00 100644 --- a/quest/m2/browser-media-qa-engines.md +++ b/quest/m2/browser-media-qa-engines.md @@ -18,8 +18,10 @@ WebCodecs encode and decode, AudioWorklet, `MediaStreamTrackProcessor`, Neither Playwright Firefox nor Playwright WebKit ships WebTransport, and WebKit also lacks the WebCodecs encoder, so neither can publish and neither -can play over QUIC. What they can do is play over the WebSocket fallback, -which is the path a real Firefox viewer takes today. +can play over QUIC. What they can do is play over the WebSocket fallback. +Real Firefox does use WebTransport; only Playwright's build lacks it, so a +fallback run covers the player, not Firefox's transport path (see open PR +[#4460](https://github.com/moq-dev/moq/pull/4460)). - Split the harness's publisher and subscriber roles so an engine can take one side: a Chromium fixture with a Firefox or WebKit player over the @@ -31,3 +33,7 @@ which is the path a real Firefox viewer takes today. a measurement of that engine, not of the player. - Playwright WebKit is not Safari. Say so in the report; a Safari defect such as #2812 still needs a manual run. + +## Related + +- [Firefox 155 WebTransport](/quest/m2/firefox-155-webtransport.md) - the real Firefox transport path, verified by hand diff --git a/quest/m2/cache-shard.md b/quest/m2/cache-shard.md index 3be996a5ae..65d7a28d55 100644 --- a/quest/m2/cache-shard.md +++ b/quest/m2/cache-shard.md @@ -58,6 +58,11 @@ where available), relay CPU and RSS at the fanout shape via behavior covered by the existing cache tests plus new ones for the staleness bound. +Decided in the 2026-09-30 audit: moved to m2 because the contention is +unmeasured on noq. [Lock wait](/quest/m1/perf/lock-wait.md) decides it; take +this up only if that shows more than 1% contention on the pool line. + ## Related +- [Lock wait](/quest/m1/perf/lock-wait.md) - measures whether the pool line is contended enough to justify this - [#3122](/quest/m1/perf/3122-moq-uring-2-5-of-relay-cpu-is-vdso-clock-reads-the-drive.md) - the remaining clock reads one layer down; a pool epoch driven by its per-turn timestamp is the way to drop the last per-frame read, and the model should drink from that cup rather than grow a second clock diff --git a/quest/m2/captions-cea.md b/quest/m2/captions-cea.md index f352869c12..e7f3be5cde 100644 --- a/quest/m2/captions-cea.md +++ b/quest/m2/captions-cea.md @@ -8,6 +8,8 @@ without extraction those broadcasts have no captions in MoQ at all. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer for timed metadata. + Parse the `user_data_registered_itu_t_t35` SEI payloads that carry CEA-708 (with 608 compatibility bytes inside) out of H.264 and H.265 access units at import, and publish the decoded cues as a `text` rendition beside the video. @@ -31,3 +33,9 @@ Whichever lands first, the import order is a contract, not an accident: caption extraction sees the SEI before stripping removes it. Running the split first silently produces a broadcast with no captions, which is the failure this quest exists to prevent, so cover the ordering with a test rather than a comment. + +## Related + +- [SEI](/quest/m2/sei.md) - carries SEI byte-faithfully as a sidecar; if + that line lands, this parser is a candidate to move onto it rather than + walking the access unit itself diff --git a/quest/m2/capture-windows.md b/quest/m2/capture-windows.md index c52a3ae046..372daead1b 100644 --- a/quest/m2/capture-windows.md +++ b/quest/m2/capture-windows.md @@ -36,6 +36,14 @@ coupling and delivers `IDirect3DSurface` frames the existing D3D11 path could take zero-copy. WGC would also answer app capture and the cursor, so weigh these three against doing that once. +Decided in the 2026-09-30 audit: the Windows half of the frame-buffer quest +folds in here. `snapshot` in `capture/window.rs` (lines 334-392) creates a +memory DC, a compatible bitmap, and a `vec![0u8; w * h * 4]` per call, then +destroys them: roughly 500 MB/s of allocation plus GDI object churn at 1080p60. +Build all three once at open in `Capture`; the pump thread owns it, so the +`!Send` handles are fine. Skip this if WGC replaces the GDI path first, and +measure before and after on a Windows host (`just rs windows`). + ## Related - [Linux capture parity](/quest/m2/capture-linux.md) - the same gaps, through diff --git a/quest/m2/catalog-tracks.md b/quest/m2/catalog-tracks.md index 4726ec7c40..d301e2dd90 100644 --- a/quest/m2/catalog-tracks.md +++ b/quest/m2/catalog-tracks.md @@ -2,42 +2,32 @@ ## Goal -Choose how a catalog describes a track over its lifetime so live playback and -recorded playback do not guess which configuration applies to a media group. -Explore immutable track definitions as an alternative to correlating catalog -updates with groups. This is independent of DVR and does not gate archives. +A track's catalog definition never changes for its name, so live and recorded +playback never guess which configuration applies to a group. A publisher whose +track configuration changes either refuses the change or publishes it under a +new track name or a new broadcast epoch. This is independent of DVR and does +not gate archives. ## Plan -There is no explicit catalog-update-to-group binding. Timestamps alone do not -establish one. Audit publishers, consumers, and catalog composition in Rust and -JS to identify which track properties change today and why: codec/config bytes, -resolution, audio layout, rendition metadata, and broadcast references. - -Compare two approaches with concrete publish and playback examples: - -- Make each track definition immutable for its identity. A configuration change - creates a new track identity instead of changing the meaning of existing - groups. This is the preferred direction to investigate, not a settled API. -- Keep mutable definitions with an explicit configuration identity or update - boundary that groups can reference. Measure the protocol and lifecycle cost - against immutable definitions rather than assuming version binding is needed. - -Distinguish immutable track definitions from a completely immutable catalog. -Decide whether catalogs may add/remove tracks or change presentation metadata, -what happens to removed track identities, and whether a name can be reused. -Cover codec changes, rendition switches, reconnects, late joiners, reordered -catalog/media delivery, and readers seeking older groups. Evaluate what catalog -state must remain discoverable when media outlives the publishing session; -retaining snapshots alone cannot establish which configuration a group uses. - -Present the recommendation and migration costs for maintainer agreement before -changing public APIs or wire semantics. Produce focused implementation quests -for the chosen design, including Rust/JS and binding synchronization, HLS/watch -behavior, draft changes, and CI regression coverage. Target any published API -break at dev. Do not add an archive-only version index or change DVR retention -as a substitute for deciding track identity. +Decided in the 2026-09-30 audit: track identity is immutable. Mutable +definitions with a configuration identity that groups reference (the old +option 2) are out, because they contradict the rule that a track name always +means the same content, and MoQ has no ETag-style invalidation. + +Audit publishers in Rust and JS for the properties that change mid-track +today (codec/config bytes, resolution, audio layout, rendition metadata) and +make each one either refuse the change or mint a new identity: a new track +name through a [catalog alias](/quest/m1/catalog-track-alias.md) when the +catalog can keep both, or a new [broadcast epoch](/quest/m1/broadcast-epoch/README.md) +when the whole broadcast restarts. The catalog may still add and remove +tracks; a removed name is never reused for different content. + +Cover codec changes, rendition switches, reconnects, and late joiners in Rust, +JS, and HLS/watch tests. Target any published API break at `dev`. ## Related -- [Archive](/quest/m1/archive/README.md) - storage and replay consume the eventual identity contract +- [Catalog track alias](/quest/m1/catalog-track-alias.md) - lets a catalog list a new track name for a changed rendition +- [Broadcast epochs](/quest/m1/broadcast-epoch/README.md) - a restart is a new epoch rather than a changed track +- [Archive](/quest/m1/archive/README.md) - storage and replay consume the identity contract diff --git a/quest/m2/closure-counters.md b/quest/m2/closure-counters.md index 04fb95b8cf..347c37f708 100644 --- a/quest/m2/closure-counters.md +++ b/quest/m2/closure-counters.md @@ -9,6 +9,8 @@ explicit fresh segment. The rule is documented as a consumer-facing contract. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer. + The stickiness fix ([moq#3625](https://github.com/moq-dev/moq/pull/3625)) keeps a departed node's last `Traffic` contribution and drops `Presence` immediately. Its review raised an open P2: retiring the gauges advances the diff --git a/quest/m2/color-catalog.md b/quest/m2/color-catalog.md new file mode 100644 index 0000000000..15318849d2 --- /dev/null +++ b/quest/m2/color-catalog.md @@ -0,0 +1,56 @@ +# [M] Catalog colour model + +## Goal + +The Hang catalog describes a video rendition's colour: primaries, transfer +characteristics, matrix coefficients, range, and the HDR10 mastering display and +content light levels. A renderer can set up its pipeline from the catalog +instead of guessing or reparsing the bitstream. + +## Plan + +Deferred to m2 in the 2026-09-30 audit: no renderer consumes colour today, so +the model waits for one. Encoder colour correctness stays in m1 as +[Encoder colour](/quest/m1/color-model.md). + +`rs/hang/src/catalog/video/mod.rs` has carried a bare `// TODO color space` since +the config was written. Two codecs already expose colour per their own syntax: +`VP9` carries primaries, transfer characteristics, matrix coefficients, and +range inside its codec string, and `AV1` has the same fields, which +`rs/moq-mux/src/codec/av1/import.rs` fills from the sequence header. They only +round-trip through muxers (the fMP4 exporter writes VP9's into `vpcC`); no +renderer consumes either, H.264 and H.265 have no equivalent, and there is no HDR10, +mastering display, or content light handling in `rs/` or `js/` at all. So an +HDR broadcast is delivered today and rendered as if it were SDR, whatever the +source signalled. + +Model the codec-neutral properties rather than one codec's syntax, so H.264 and +H.265 VUI, AV1 colour config, and a container's `colr` box all populate the same +fields. Generalize the existing VP9 and AV1 fields into that model rather than +adding a second colour source beside them: the codec-specific fields keep their +wire meaning (the VP9 codec string is defined by its spec) and feed the neutral +fields at import, so a renderer reads one place. Fill them at import from +whichever source the rendition provides, and emit them on export. + +Settle the precedence before adding the fields, because these sources disagree +in real files and a renderer must not see a different answer per import path. +Prefer the bitstream over the container: VUI and the AV1 colour config are what +the encoder actually signalled and travel with the elementary stream, while a +`colr` box is added by a muxer that may be wrong or stale. Record which source +won so a conflict is diagnosable rather than invisible, and treat an +unspecified value as absent rather than as a signalled default, so a partial +container box does not override a complete bitstream. + +Static display properties belong here rather than in a timed track, which is +also where the HDR10 metadata that lives in H.26x SEI should land once it can be +read. That is the one seam with the SEI line, and it runs in one direction: +this quest gives display metadata a home, and does not depend on SEI work. + +Test each source of truth in isolation, a source that signals nothing, a +conflict between VUI and container resolving to the bitstream, a container box +that fills a gap the bitstream left unspecified, and an SDR round trip that +stays byte-identical. + +## Related + +- [Encoder colour](/quest/m1/color-model.md) - encoders signal the colour they actually produce diff --git a/quest/m2/cpp-vcpkg.md b/quest/m2/cpp-vcpkg.md index 33f026e32b..646839fbbe 100644 --- a/quest/m2/cpp-vcpkg.md +++ b/quest/m2/cpp-vcpkg.md @@ -27,3 +27,7 @@ CI on Windows, macOS, and Linux. ## Required - [Package](/quest/m1/cpp/package.md) - the release tarballs the port fetches + +## Related + +- [Conan remote](/quest/m3/cpp-conan.md) - the same tarball through Conan diff --git a/quest/m2/emsg.md b/quest/m2/emsg.md index a9ceb774ba..77af76c581 100644 --- a/quest/m2/emsg.md +++ b/quest/m2/emsg.md @@ -13,6 +13,8 @@ it rather than restating it. Deferred SEI extraction is not a prerequisite. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer for timed metadata. + The shared contract, recorded here and mirrored in the Hang draft: - Publish an event as soon as it is received, on its own group sequence; never @@ -57,3 +59,6 @@ byte-identical. - [AV1 metadata OBUs](/quest/m2/av1-metadata.md) - the same silent drop in a different layer +- [ID3 catalog section](/quest/m2/id3.md) - gives one payload type carried here a + typed contract +- [FLV script tags](/quest/m2/flv-script.md) - likewise diff --git a/quest/m2/flate.md b/quest/m2/flate.md index 41b3b3cba0..04d30073a5 100644 --- a/quest/m2/flate.md +++ b/quest/m2/flate.md @@ -1,18 +1,40 @@ -# Compressed tracks +# [M] Flate tracks in every binding ## Goal Opaque tracks, compressed per group or not, are published and consumed the same way from every language, not only Rust and JS, and the bytes on the wire -are identical across all of them. +are identical across all of them. A track published from a wrapper decodes in +the browser with `@moq/flate` and vice versa. ## Plan +Decided in the 2026-09-30 audit: collapse the line into this quest and follow +the `json` namespace pattern that [#4526](https://github.com/moq-dev/moq/pull/4526) +sets, rather than the per-wrapper siblings of the old bindings quest. + `moq-flate` and `@moq/flate` absorb `moq-binary`'s snapshot and stream modes in moq-binary's fold into moq-flate ([#4425](https://github.com/moq-dev/moq/pull/4425), on `dev`), so the crate -already owns the per-group window a caller could otherwise desynchronize. The -track wrapper this line once planned was dropped for that reason. What -remains is reaching those tracks from the hand-written binding wrappers. +already owns the per-group window a caller could otherwise desynchronize. +Bind those track modes, not the bare codec: a `frame()` call across the FFI +boundary invites the window desync the track modes exist to prevent. + +#4526 removes moq-ffi's `publish_binary_*` and adds `MoqFlate*Producer::new` +constructed from a track producer, as `json` does. What remains: + +- moq-ffi has no consume side for flate tracks. Add consumers mirroring the + `json` ones so each wrapper can read what it writes. +- Each wrapper (Python, Swift, Kotlin, Go, Dart) gains a `flate` namespace in + the same place and shape as its `json` one. +- Document in `doc/lib/{py,swift,kt,go,dart}` beside the JSON entry. +- Tests: a round trip in each wrapper that has tests, and one cross-language + check that a wrapper-published group decodes with `@moq/flate`. Run + `just test interop --all`. + +Public API: additive on top of #4526. Wire, catalog, and relay: none. +Compression stays invisible to `moq-net`; a compressed track is announced, +routed, and cached like any other. + +## Required -No wire, catalog, or relay impact. Compression stays invisible to `moq-net`; -a compressed track is announced, routed, and cached like any other. +- [JSON](/quest/m1/ffi-shape/json.md) - sets the `json` and `flate` namespace pattern this follows diff --git a/quest/m2/flv-script.md b/quest/m2/flv-script.md index 9af5457ca4..0d90021f9c 100644 --- a/quest/m2/flv-script.md +++ b/quest/m2/flv-script.md @@ -7,6 +7,8 @@ FLV script tags survive RTMP and FLV import instead of being discarded, so ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer for timed metadata. + `rs/moq-mux/src/container/flv/import.rs` matches `TAG_SCRIPT => {}` and moves on, which drops every AMF data message an encoder sends. `onMetaData` is the one every RTMP publisher emits, and applications routinely push their own cues diff --git a/quest/m2/gop-overhead.md b/quest/m2/gop-overhead.md index 9c9a8649b3..e19576e6d0 100644 --- a/quest/m2/gop-overhead.md +++ b/quest/m2/gop-overhead.md @@ -36,3 +36,7 @@ this quest. ## Closes - [#2284](https://github.com/moq-dev/moq/issues/2284) - close this issue when the quest finishes + +## Related + +- [Intra-refresh GOPs](/quest/m2/intra-refresh/README.md) - refresh encoding is the flat-bitrate alternative this verdict prices diff --git a/quest/m2/id3.md b/quest/m2/id3.md index e952b0d5d1..dcec002c74 100644 --- a/quest/m2/id3.md +++ b/quest/m2/id3.md @@ -10,6 +10,8 @@ stream. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer for timed metadata. + Define the catalog section and frame contract for complete ID3 tags on the shared event contract [emsg](/quest/m2/emsg.md) settles. Publish tags when received on independently sequenced groups, with their presentation time on @@ -34,3 +36,8 @@ with default and non-default PMT descriptors, multiple tags, unknown frames, large tags spanning PES packets, timestamp wrap, discontinuity, and an ID3-only program. Include non-ID3 and malformed stream type `0x15` fixtures that remain generic. + +## Related + +- [SEI sidecars](/quest/m2/sei.md) - the separate codec metadata + contract diff --git a/quest/m2/intra-refresh/README.md b/quest/m2/intra-refresh/README.md index cd3343c60a..c606b28fe0 100644 --- a/quest/m2/intra-refresh/README.md +++ b/quest/m2/intra-refresh/README.md @@ -5,9 +5,14 @@ Video encoded with periodic intra refresh has no keyframes. Each frame refreshes a stripe of the picture, so a decoder that starts at the beginning of a sweep is clean once the sweep completes, and the bitrate never spikes. This questline -makes such video a first-class hang broadcast at both ends: our encoders can -emit it, streams contributed that way import cleanly, and every viewer tunes in -without a visible glitch. +makes such video a first-class hang broadcast on import and playback: streams +contributed that way import cleanly, and every viewer tunes in without a +visible glitch. + +Decided in the 2026-09-30 audit: the encode side (shared config, NVENC, V4L2, +bindings) moved to m3 pending the [GOP overhead](/quest/m2/gop-overhead.md) +verdict. Import and playback stay here because contributed feeds already use +intra refresh regardless of what our encoders do. The motivations, in the order they settle tradeoffs: a flat bitrate at low latency, so a bandwidth grant holds; faster tune-in, since a short refresh cycle @@ -29,12 +34,8 @@ Decisions the quests share: mid-stream skip both decode everything and present nothing until recovery, freezing on the last good frame. A group that opens on a true IDR shows at once. -- The shared encode config extends the `Gop` contract settled in main, - and a cut in refresh mode starts a new sweep, never an IDR. - H.264 and H.265 only. AV1 and VP9 have no standard gradual refresh signal. - WebCodecs has no intra-refresh option, so js/publish is consumer-only here. - Backends without the knob refuse refresh mode; NVENC and V4L2 get it now, - Media Foundation and MediaCodec are follow-ups. + WebCodecs has no intra-refresh option, so js/publish is consumer-only. ## Required @@ -48,3 +49,5 @@ Decisions the quests share: - [Audio warmup](/quest/m1/audio-warmup.md) - Opus convergence after a mid-stream join uses the same `warmup` field - [Open-GOP leading pictures](/quest/m1/open-gop-leading-pictures.md) - frames stamped before the group's keyframe are the other tune-in trim - [Catalog warmup](/quest/m1/catalog-warmup.md) - the generic `warmup` field this line reads, kept in m1 for audio and open-GOP tune-in +- [GOP overhead](/quest/m2/gop-overhead.md) - the verdict that decides whether our encoders emit refresh mode +- [Encode config](/quest/m3/intra-refresh-encode-config.md) - refresh-mode groups on the `Gop` contract, parked with NVENC, V4L2, and bindings behind it diff --git a/quest/m2/ladder/README.md b/quest/m2/ladder/README.md index 3b1867c2b5..ebb9027691 100644 --- a/quest/m2/ladder/README.md +++ b/quest/m2/ladder/README.md @@ -15,6 +15,9 @@ state. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer for the +publisher-side ladder. + The catalog and player half already shipped in [moq#2865](https://github.com/moq-dev/moq/pull/2865): the optional `stalled` state exists in `rs/hang`, `js/hang`, `rs/moq-msf`, `js/msf`, the HANG draft diff --git a/quest/m2/mobile-capture-android.md b/quest/m2/mobile-capture-android.md index 1cdd5cbe29..7482917a80 100644 --- a/quest/m2/mobile-capture-android.md +++ b/quest/m2/mobile-capture-android.md @@ -21,6 +21,10 @@ decides whether XL is worth spending. `moq-tokio` already reaches into Android through JNI for `tls::init_android`, so the mechanism exists. +## Required + +- [Ownership boundary](/quest/m1/mobile-ownership.md) - decides whether an NDK/JNI backend family is worth building + ## Related - [iOS capture](/quest/m2/mobile-capture-ios.md) - the other half of mobile, which diff --git a/quest/m2/mobile-capture-ios.md b/quest/m2/mobile-capture-ios.md index 011835b65d..b24bcb8e0b 100644 --- a/quest/m2/mobile-capture-ios.md +++ b/quest/m2/mobile-capture-ios.md @@ -22,6 +22,10 @@ Reuse the `capture::Source` shape the other platforms use rather than growing an iOS-specific entry point, so device enumeration and selection behave the same everywhere. +## Required + +- [Ownership boundary](/quest/m1/mobile-ownership.md) - decides whether Rust owns capture on mobile at all + ## Related - [Android capture and encode](/quest/m2/mobile-capture-android.md) - the other half of diff --git a/quest/m2/mobile-completion.md b/quest/m2/mobile-completion.md index ede55d9f7a..8490656ed7 100644 --- a/quest/m2/mobile-completion.md +++ b/quest/m2/mobile-completion.md @@ -26,6 +26,7 @@ Do not close #700 merely because its next subset or a design decision finished. - [Dart codec parity](/quest/m1/dart-codecs.md) - codec-enabled artifacts and Dart video consumer integration - [iOS capture](/quest/m2/mobile-capture-ios.md) - deliver the selected iOS capture path - [Android capture](/quest/m2/mobile-capture-android.md) - deliver the selected Android capture and codec path +- [Mobile ownership](/quest/m1/mobile-ownership.md) - select and scope the mobile media architecture ## Closes diff --git a/quest/m2/moq-install-url.md b/quest/m2/moq-install-url.md index 9a9eadef2e..bcda57218e 100644 --- a/quest/m2/moq-install-url.md +++ b/quest/m2/moq-install-url.md @@ -8,6 +8,9 @@ maintaining another copy of the install logic. ## Plan +Deferred to m2 in the 2026-09-30 audit: it follows [the +installer](/quest/m2/moq-installer.md), which is m2. + - Implement the hosting change in **moq-dev/moq.dev**, which owns the root website, not moq.pro. Track this cross-repository work here beside its installer dependency; complete this quest only after the website change diff --git a/quest/m2/moq-installer.md b/quest/m2/moq-installer.md index d5d5cfa7df..c5c88937c0 100644 --- a/quest/m2/moq-installer.md +++ b/quest/m2/moq-installer.md @@ -1,4 +1,4 @@ -# [M] One-command moq installation and upgrades +# [S] One-command moq installation and upgrades ## Goal @@ -14,67 +14,39 @@ service setup, Windows support, or new release targets. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer; `cargo install`, +Nix, Docker, and winget already install `moq`. + - Default to the latest stable `moq-cli` release, with an explicit version option. Resolve that product's tags, not the repository-wide latest release: this repository publishes multiple independently versioned crates. Refuse missing versions, malformed input, and incomplete releases clearly. -- Reuse the release archives and `SHA256SUMS`. Verify the selected archive - before extracting and installing its expected executable. Stage and check - the replacement before modifying the destination. Commit the executable and - ownership record as one recoverable transaction on the destination - filesystem: stage the new pair, retain the validated prior pair, and write a - durable journal before either rename. Record distinct phases after the - executable rename and after the ownership-record rename. On a handled - failure, roll back both files. After interruption, the next run must use the - journal to complete the new pair when both staged objects validate together. - If both renames completed, validate the installed pair and finish cleanup; - otherwise restore the prior pair, or remove every transaction file for an - initial install. Recovery must not misclassify a partial transaction as an - unmanaged installation. - Flush staged files, journal updates, renames, and their directory entries at - the required commit boundaries. Remove the journal and backups only after the - matching pair is durable. This is the atomic installation contract: recovery - exposes either the complete old pair or the complete new pair, never a mixed - pair. A failed initial install leaves no destination, while a failed upgrade - leaves the prior executable and ownership record usable. Clean up temporary - files after commit or rollback. +- Verify the selected archive against the release's `SHA256SUMS`, extract it + into a temporary file in the destination directory, then atomically rename + it over the destination. A failed install leaves the prior binary untouched. + Decided in the 2026-09-30 audit: no journaled transaction or ownership + record; that was out of proportion for one binary. - Support the existing targets: macOS ARM64 and Linux x86_64/ARM64 with glibc 2.34 or newer. Refuse unsupported operating systems, architectures, - and libc variants with actionable diagnostics. Intel macOS and musl/Alpine - require separate release work. + and libc variants with actionable diagnostics. - Default to `~/.local/bin` with an explicit directory override. Do not invoke - sudo or edit shell profiles. Print the installed version and path, and - shell-appropriate PATH instructions when needed. Detect when another - `moq` on PATH would take precedence so success does not imply the wrong - binary will run. Do not follow an existing destination symlink into a - package manager's installation or overwrite a conflicting unmanaged file. - Keep a durable ownership record bound to the destination and installed - binary digest. Refuse replacement when the record is missing, malformed, - or mismatched, including a record copied from another destination. A valid - prior installation can be replaced; failed upgrades must preserve both its - binary and usable ownership record. -- Keep the canonical script and its tests in this repository. Publish a - usable HTTPS source for the dependent website quest; that quest exposes - `https://moq.dev/install.sh` without duplicating installer logic. -- Document first install, latest-version upgrade, explicit version selection, - directory override, PATH setup, and removal in `doc/setup/install.md`. - Use the working canonical URL until the website quest switches the example. - Package-manager installations continue to use their package manager for - upgrades. Describe only the subcommands the selected release actually ships. -- Wire installer tests into `just check` and CI. Cover initial - install, repeat install, upgrade, explicit downgrade, product-specific latest - selection, unsupported hosts, corrupt/missing assets, destination conflicts, - and failure preserving an existing executable. Use controlled fixtures for - failure cases, including interruption before and after every journal, rename, - durability, and cleanup boundary. Explicitly cover the post-second-rename, - pre-cleanup state. Assert that each case completes the new pair or restores - the old pair, and that an initial-install failure leaves neither file. Add - native macOS/Linux smoke coverage for executable startup. - Exercise the canonical script with real release assets in a temporary - install directory and run the installed `moq --version`. The dependent - website quest owns verification of the final public URL. + sudo or edit shell profiles. Print the installed version and path, and PATH + instructions when needed. Warn when another `moq` on PATH takes precedence. + Refuse a destination that is a symlink, so a package manager's install is + never overwritten. +- Keep the canonical script and its tests in this repository, and publish a + usable HTTPS source for [the install URL](/quest/m2/moq-install-url.md). +- Document install, upgrade, version selection, directory override, and + removal in `doc/setup/install.md`. +- Wire installer tests into `just check` and CI: initial install, upgrade, + explicit downgrade, product-specific latest selection, unsupported hosts, + corrupt or missing assets, and a failure preserving the existing binary. + Run the script against real release assets in a temporary directory and + run the installed `moq --version`. ## Related - [Binary release workflow](/quest/m1/tooling/release-binary.md) - reuse its artifacts without requiring workflow consolidation +- [`moq relay`](/quest/m2/moq-relay-subcommand.md) - relay functionality joins + the same executable independently of its installation method diff --git a/quest/m2/moq-relay-subcommand.md b/quest/m2/moq-relay-subcommand.md index 0c0453d704..88833abf60 100644 --- a/quest/m2/moq-relay-subcommand.md +++ b/quest/m2/moq-relay-subcommand.md @@ -9,6 +9,9 @@ decode, and render stack. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer, and [`moq --listen` +admission](/quest/m1/cli-serve.md) no longer waits on it. + `moq-relay` is lib+bin (`rs/moq-relay/Cargo.toml`), with a 15-line `main.rs` that installs the aws-lc-rs provider, optionally jemalloc, and calls `Relay::load(Config::load()?).await?.run()`. diff --git a/quest/m2/msfts-convergence.md b/quest/m2/msfts-convergence.md index fb344dd510..ac9a67dc4a 100644 --- a/quest/m2/msfts-convergence.md +++ b/quest/m2/msfts-convergence.md @@ -4,19 +4,23 @@ A subscriber author can map this repository's demultiplexed TS lane (access units, Hang catalog `mpegts` section) onto MSFTS ES-level carriage -without guessing. #3731 settled four of six divergences (scope, group -alignment as a SHOULD, mux rate, egress timing); the payload unit (access units -versus filtered 188-octet packets) is MSFTS's call at msfts#33, and SI -repetition is now per `table_id` here versus a subscriber obligation there. -Transporting TS verbatim is a non-goal. +without guessing. Transporting TS verbatim is a non-goal. ## Plan -Once msfts#33 settles, re-read the draft and decide what converges: a -published mapping from the `mpegts` catalog section to the `m2ts` fields, or a -change on either side. Update `drafts/draft-lcurley-moq-mpegts.md` and -`doc/concept` with whatever lands. +Decided in the 2026-09-30 audit: moved from m4 and the msfts#33 gate removed. +msfts#33 closed on 2026-09-28, answered by msfts#36 (merged 2026-09-24), and +egress moved to msfts#37 (closed). Two differences remain: -## Required +- **Program tables.** MSFTS carries them in tracks; here they live in the + Hang catalog's `mpegts` section. Decide whether to publish a mapping from + the catalog section to MSFTS's table tracks, or change either side. +- **ES units.** MSFTS es-units carry the whole PES packet; here they carry the + payload plus `stream_id`. Decide whether the PES header fields we drop + matter to a subscriber, and converge or document the mapping. -- msfts#33 (https://github.com/mondain/msfts/issues/33) settles the ES-level payload unit +Update `drafts/draft-lcurley-moq-mpegts.md` and `doc/concept` with whatever +lands. Open TS PRs [#4577](https://github.com/moq-dev/moq/pull/4577) (per-ES +access units at export), [#4579](https://github.com/moq-dev/moq/pull/4579) +(export on the mux rate), and [#4580](https://github.com/moq-dev/moq/pull/4580) +(per-program SI) touch the same area; land or rebase on them first. diff --git a/quest/m2/nvenc-pool.md b/quest/m2/nvenc-pool.md index c1723df728..bd6c851e7f 100644 --- a/quest/m2/nvenc-pool.md +++ b/quest/m2/nvenc-pool.md @@ -15,4 +15,10 @@ quest and report the numbers. frames) on every `encode`, and frees them all at the end of the call. Keep a small pool sized by the frames in flight, which is one today since B-frames are off. Re-register a CUDA resource only when its pointer changes. Measure -with the `encode-presets` example from #4099. +with the `encode-presets` example from #4099, which so far exists only on the +[OBS moq-video](/quest/m1/obs-moq-video/README.md) line; if that line has not +merged, run it from that branch or add an encode benchmark on main instead. + +## Related + +- [OBS moq-video](/quest/m1/obs-moq-video/README.md) - carries the `encode-presets` example this measures with diff --git a/quest/m2/obs-decode-linux.md b/quest/m2/obs-decode-linux.md index e9ef5a7aac..cd5c24376d 100644 --- a/quest/m2/obs-decode-linux.md +++ b/quest/m2/obs-decode-linux.md @@ -14,3 +14,7 @@ At least one supported Linux hardware decoder delivers frames to OBS without CPU ## Required - [Video source replacement](/quest/m1/obs-moq-video/source.md) - native frame contract and fallback lifecycle + +## Related + +- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - owns remaining VAAPI decode and native surface gaps; avoid a duplicate backend implementation diff --git a/quest/m2/one-port/README.md b/quest/m2/one-port/README.md index 995ece2737..d7c055b883 100644 --- a/quest/m2/one-port/README.md +++ b/quest/m2/one-port/README.md @@ -20,6 +20,9 @@ reuseport shard groups are a later consumer, not a blocker. ## Plan +Deferred to m2 in the 2026-09-30 audit: the consumer is moq.pro's edge, SRT is +blocked upstream on srt-tokio, and STUN's only consumer is P2P, also m2. + ### Classifying a datagram RFC 7983 already partitions the first byte: STUN is 0 to 3, DTLS 20 to 63, @@ -73,6 +76,11 @@ pre-accepted streams can stand behind. ## Required -- [UDP demux](/quest/m2/one-port/udp-demux.md) - one socket carries QUIC, STUN answers, and the WebRTC media path, with greasing off +- [UDP demux](/quest/m2/one-port/udp-demux.md) - one socket carries QUIC and STUN answers, with greasing off and a WebRTC hook for embedders - [TCP acceptor](/quest/m2/one-port/tcp-demux.md) - one listener carries TLS-terminated HTTP, RTMP, and RTMPS - [SRT on the shared socket](/quest/m2/one-port/srt-demux.md) - srt-tokio accepts a virtual socket and the flow table pins its 4-tuples + +## Related + +- [P2P](/quest/m2/p2p/README.md) - the client that names the relay as its STUN server +- [Stream sessions](/quest/m2/uring-tcp/README.md) - the io_uring workers that would host the same demux later diff --git a/quest/m2/one-port/udp-demux.md b/quest/m2/one-port/udp-demux.md index 943a1bf18d..9b44ff448c 100644 --- a/quest/m2/one-port/udp-demux.md +++ b/quest/m2/one-port/udp-demux.md @@ -2,11 +2,12 @@ ## Goal -`moq-relay` reads one UDP socket and serves QUIC, STUN Binding answers, and -the WebRTC media path from it. A WHIP or WHEP client sees ICE candidates on -the QUIC port, a P2P client can list `stun::`, and every -backend config has QUIC-bit greasing off so a short header is always -recognizable. +`moq-relay` reads one UDP socket and serves QUIC and STUN Binding answers from +it. A P2P client can list `stun::`, and every backend config has +QUIC-bit greasing off so a short header is always recognizable. The WebRTC +media path is an embedder hook: the demux hands its class to a virtual socket +an embedder such as moq.pro's edge can feed to `moq-rtc`, since `moq-relay` +serves no WHIP or WHEP. ## Plan @@ -38,12 +39,19 @@ one is a public query and goes to the responder. Public STUN clients never send USERNAME and ICE agents always do. Off by default in `moq-relay`, on with `--stun`. -WebRTC: `moq_rtc::server::mux::Mux` gains `Mux::feed(src, bytes)` and a -constructor over a virtual socket, so it stops binding its own port; its -advertised candidates become the shared address. `Config.udp_bind` goes. -When ICE succeeds on a 4-tuple the mux reports it and the outer flow table -pins that tuple to WebRTC, so later RTP cannot be classified as SRT. +WebRTC: DTLS, RTP, and USERNAME-carrying STUN go to a `webrtc` virtual +socket that `moq-relay` leaves unconsumed and an embedder takes. Adapting +`moq_rtc::server::mux::Mux` to it (`Mux::feed`, a shared advertised address, +and pinning ICE 4-tuples in the flow table) is the embedder's work, not this +quest's. -Tests: a unit test per first-byte class routes to the right virtual socket; -an integration test runs a QUIC client, a STUN Binding round trip, and a WHIP -session against one bound port. `moq-relay` docs list the port once. +Decided in the 2026-09-30 audit: narrowed to QUIC plus STUN in the relay, +because `moq-relay` has no `moq-rtc` dependency and serves no WHIP or WHEP. + +Tests: a unit test per first-byte class routes to the right virtual socket, +including the WebRTC hook; an integration test runs a QUIC client and a STUN +Binding round trip against one bound port. `moq-relay` docs list the port once. + +## Related + +- [P2P](/quest/m2/p2p/README.md) - the client side of the STUN answer diff --git a/quest/m2/p2p/README.md b/quest/m2/p2p/README.md index 3ba6d674cf..f6e40ddb04 100644 --- a/quest/m2/p2p/README.md +++ b/quest/m2/p2p/README.md @@ -28,13 +28,14 @@ under "One channel, qmux, ordered". ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer. + ### The relay is the rendezvous and the fallback Every opted-in peer already holds a session to the same relay, so discovery and signaling ride that session as ordinary moq broadcasts under a reserved -prefix (`.p2p/` by default, configurable), the same shape -[carrier voice](/quest/m2/carrier-voice/README.md) uses for call setup. The -relay learns nothing new; trust is its token scope. A peer that may publish +prefix (`.p2p/` by default, configurable). The relay learns nothing new; +trust is its token scope. A peer that may publish under the prefix is as trusted as any publisher the token admits, so this line needs no E2EE. diff --git a/quest/m2/p2p/cost-scopes.md b/quest/m2/p2p/cost-scopes.md index 7fb8c57ff5..fd0a2e5b2c 100644 --- a/quest/m2/p2p/cost-scopes.md +++ b/quest/m2/p2p/cost-scopes.md @@ -46,3 +46,7 @@ belongs in the mesh too. Write the rule beside route selection in `just drafts check`. Then update [watch](/quest/m2/p2p/watch.md) and [transit](/quest/m2/p2p/transit.md) with the chosen rule and open the implementation quest it needs. + +## Related + +- [Cluster routing](/quest/m1/cluster-routing/README.md) - the mesh-side use of cost diff --git a/quest/m2/p2p/transit.md b/quest/m2/p2p/transit.md index 32ee77bd3c..cde7482763 100644 --- a/quest/m2/p2p/transit.md +++ b/quest/m2/p2p/transit.md @@ -1,37 +1,35 @@ -# [M] Transit in the JS origin +# [S] Transit in the JS origin ## Goal -A tab forwards the routes it receives on one session to the sessions it -serves, so a watcher can re-serve a broadcast to a peer and a room of -watchers can cost the relay one egress stream. Split horizon holds: a route +A test proves a tab forwards the routes it receives on one session to the +sessions it serves, so a watcher can re-serve a broadcast to a peer and a room +of watchers can cost the relay one egress stream. Split horizon holds: a route is never announced back to the session it arrived on, and every forwarded chain carries the tab's hop id. ## Plan -The JS origin keeps two tables so a received entry can never be announced -back to a peer; that separation stays. Transit adds the forwarding step: a -route received on session A is announced on every other attached session -with the tab's hop id appended to `hops`, the receiving link's cost added -per [cost across scopes](/quest/m2/p2p/cost-scopes.md) once it exists and -`Cost::UNKNOWN` semantics until then, and dropped when the chain reaches -`MAX_HOPS` or already contains the tab's id. A retraction forwards the same -way. - -Serving a forwarded route means the tab subscribes upstream once and fans out -from its own origin, which is what the Rust origin already does with -`with_publisher` and `with_subscriber`; the JS origin gains the same -splice-and-share behavior for remote routes it serves onward. +Decided in the 2026-09-30 audit: shrink to a test. The Rust origin already +does transit (`with_publisher` and `with_subscriber` splice and share remote +routes), and [Generated lite](/quest/m1/rs2ts/lite.md) replaces the +hand-written JS origin with one generated from it, so there is no JS +forwarding step to write by hand. The hop id must be one per origin, not per session, so the roster id from [signaling](/quest/m2/p2p/signal.md) and the id in forwarded chains agree. -Tests with the mock transport pair: a route received on A appears on B with -the id appended and never on A; a chain already containing the id is dropped; -a retraction on A retracts on B; two watchers of one tab share one upstream -subscription. +Tests with the mock transport pair against the generated origin: a route +received on A appears on B with the id appended and never on A; a chain +already containing the id or at `MAX_HOPS` is dropped; a retraction on A +retracts on B; two watchers of one tab share one upstream subscription. If a +case fails, the fix goes in the Rust origin. + +## Required + +- [Generated lite](/quest/m1/rs2ts/lite.md) - the JS origin generated from moq-net, which already does transit ## Related - [Watch opts in](/quest/m2/p2p/watch.md) - the first topology that needs a forwarding tab +- [Cost across scopes](/quest/m2/p2p/cost-scopes.md) - adds the receiving link's cost to forwarded routes diff --git a/quest/m2/p2p/webrtc.md b/quest/m2/p2p/webrtc.md index 4e88a289f1..6dfe54fc1f 100644 --- a/quest/m2/p2p/webrtc.md +++ b/quest/m2/p2p/webrtc.md @@ -43,3 +43,4 @@ candidate is offered and selected. Browser interop is the harness's job. - [Data channel transport](/quest/m2/p2p/transport.md) - the browser side of the same binding - [moq-cli joins](/quest/m2/p2p/cli.md) - the first consumer +- [One port](/quest/m2/one-port/README.md) - the relay-side STUN answer this client can be pointed at diff --git a/quest/m2/processor/README.md b/quest/m2/processor/README.md index 9d2fefef6a..401ddba58f 100644 --- a/quest/m2/processor/README.md +++ b/quest/m2/processor/README.md @@ -26,6 +26,9 @@ trailing, so the processor claims its prefix and mirrors the source path beneath it. The source's catalog reaches the output through a cross-broadcast reference ([media contract](/quest/m2/processor/media-contract.md)). +Deferred to m2 in the 2026-09-30 audit: no processor customer is committed, +and its end-to-end proof (processor-vision) was deleted. + ## Required - [Processor media contract](/quest/m2/processor/media-contract.md) - define diff --git a/quest/m2/processor/grant-lease.md b/quest/m2/processor/grant-lease.md index 66ee0bf16a..b28462b6c4 100644 --- a/quest/m2/processor/grant-lease.md +++ b/quest/m2/processor/grant-lease.md @@ -22,8 +22,8 @@ cooperative worker timer. Build on what exists: `Grant::deadline` (`rs/moq-auth/src/grant.rs`, #4237) already pins an accepted grant to a fixed deadline, and dev's `moq_auth::lease` (#3943) re-checks a session on cadence and reports why it ended. Extend those to the handles a grant opened rather -than adding a second timer. No clock-skew grace: open #4368 makes expiry -exact and drops `CLOCK_SKEW`, so a deadline in the past is expired. +than adding a second timer. No clock-skew grace: #4368 (merged) made expiry +exact and dropped `CLOCK_SKEW`, so a deadline in the past is expired. Cover an idle open handle, active source reads, active publication, refresh before expiry, refresh after demand ends, disconnect races, HTTP and HLS rejection of diff --git a/quest/m2/processor/media-contract.md b/quest/m2/processor/media-contract.md index 1bcf95eb5f..27c4f3d8cd 100644 --- a/quest/m2/processor/media-contract.md +++ b/quest/m2/processor/media-contract.md @@ -31,3 +31,7 @@ Land Rust and JavaScript catalog bindings, resolver behavior, and fixtures for video, audio, text, missing output, malformed relations, lazy resolution, and relative-path escape. The release and the moq.pro (downstream) pin rollout stay out of this quest. + +## Required + +- [Catalog track alias](/quest/m1/catalog-track-alias.md) - renditions from a source and an output broadcast that share a track name would otherwise collide in one catalog diff --git a/quest/m2/quic-ack-hook.md b/quest/m2/quic-ack-hook.md index 3f2bdc9b2d..2879ba8da3 100644 --- a/quest/m2/quic-ack-hook.md +++ b/quest/m2/quic-ack-hook.md @@ -9,10 +9,13 @@ guess. ## Plan -The work lives in `moq-dev/web-transport`. This quest exists so the release -it produces is one condition dependents wait on; it supersedes the design in -moq-dev/web-transport#368, whose snapshot-counter shape loses the wakeups a -latency sample needs. +The trait method lives in `moq-dev/web-transport`. The `web-transport-moq` +adapter lives in the moq-dev/noq fork and is released from it +(Cargo.toml:236-237), so its implementation folds into +[ACK progress](/quest/m2/quic-ack-progress.md)'s release rather than a +separate one. This quest exists so the release it produces is one condition +dependents wait on; it supersedes the design in moq-dev/web-transport#368, +whose snapshot-counter shape loses the wakeups a latency sample needs. Add one method to `SendStream`, in the trait's poll style: @@ -30,7 +33,7 @@ use. A default body cannot construct a backend's own `Self::Error`, so unsupported has to live in the return type rather than the error, and a consumer must treat `None` as unknown, never as delivered. -Implement it in `web-transport-moq` over the fork's accessor. Leave +Implement it in `web-transport-moq`, in the fork, over the fork's accessor. Leave `web-transport-wasm` on the default; the browser's `WebTransportSendStream.getStats()` is unimplemented in shipping Chrome and its `bytesAcknowledged` is at risk in the W3C draft. qmux over a reliable transport may treat serialization as @@ -42,5 +45,21 @@ the middle of an in-flight frame, several waiters on one stream in offset order, a waiter whose offset lies beyond the final size, reset by sender, STOP_SENDING by the receiver, and session close. -Cut releases of `web-transport-trait` and `web-transport-moq`. The quest -completes when both are on crates.io. +Cut a `web-transport-trait` release, and ship the adapter in the fork +release that carries ACK progress. The quest completes when both are on +crates.io. + +Decided in the 2026-09-30 audit: moved to m2 with its only consumer, +[frame-granularity starvation](/quest/m2/starvation-frames.md). + +## Required + +- [Per-stream ACK progress in noq](/quest/m2/quic-ack-progress.md) - the + noq-proto accessor the adapter reads, released together + +## Related + +- [Starvation at frame granularity](/quest/m2/starvation-frames.md) - the + moq-net consumer +- [qmux on the QUIC stream state machine](/quest/m2/quic-qmux.md) - decides + what acknowledgment means over a reliable transport diff --git a/quest/m2/quic-ack-progress.md b/quest/m2/quic-ack-progress.md index b87ca0b86b..839ad1b145 100644 --- a/quest/m2/quic-ack-progress.md +++ b/quest/m2/quic-ack-progress.md @@ -33,6 +33,16 @@ stream teardown: the accessor must not return a prefix that includes bytes the peer will never acknowledge, and a waiter for an offset beyond the final size must resolve with the reset instead of hanging. -Land it in the fork and offer it upstream once it is stable. The quest +Land it in the fork and offer it upstream once it is stable. The +`web-transport-moq` half of [the ACK hook](/quest/m2/quic-ack-hook.md) lives +in the same fork repository and ships in the same release. The quest completes when a `moq-noq-proto` release carries the accessor and `Cargo.lock` here can name it. + +Decided in the 2026-09-30 audit: moved to m2 with its consumers, the ACK +hook and [frame-granularity starvation](/quest/m2/starvation-frames.md). + +## Related + +- [poll_acked in web-transport](/quest/m2/quic-ack-hook.md) - the first + consumer of the accessor diff --git a/quest/m2/quic-bbr-loss-parity.md b/quest/m2/quic-bbr-loss-parity.md index 7b36bf4f53..5a2cb439a6 100644 --- a/quest/m2/quic-bbr-loss-parity.md +++ b/quest/m2/quic-bbr-loss-parity.md @@ -31,6 +31,10 @@ first ACK sample, and a loss between ACKs that must not alter the next ACK's sample. Retain the spurious-loss undo coverage through Refill. Internal only; no `Controller` or wire change. +Decided in the 2026-09-30 audit: moved to m2. The seven correctness fixes +already shipped, and this remaining gap has no observed impact on MoQ +traffic yet. + ## Related - [Upstream the fork](/quest/m1/quic/upstream.md) - offers this fix alongside the seven diff --git a/quest/m2/quic-bbr-natural-drain.md b/quest/m2/quic-bbr-natural-drain.md index 41f5bce0a2..5ca6de5d3b 100644 --- a/quest/m2/quic-bbr-natural-drain.md +++ b/quest/m2/quic-bbr-natural-drain.md @@ -1,14 +1,22 @@ -# [M] Measure natural draining before BBR ProbeRTT +# [L] BBR media study ## Goal -A measured adopt-or-retain decision on allowing natural media drains to -satisfy ProbeRTT. Reduce demonstrated frame deadline interference without +Measured adopt, retain, or investigate-further decisions on three BBR +behaviors under media traffic: allowing natural media drains to satisfy +ProbeRTT, recognizing bandwidth growth within a round, and precautionary +bandwidth probing. Reduce demonstrated frame deadline interference without stale minimum RTT, persistent queues, or unfairness. Application limitation -alone is not evidence that the path drained. +alone is not evidence that the path drained, and Google parity is not +assumed to be an improvement. ## Plan +Decided in the 2026-09-30 audit: the Google BBR comparison merged here as +one study, since both reuse the same media profiles, harness, and baseline. + +### Natural drains + Use the corrected released fork as baseline. ProbeRTT targets half the estimated BDP, floored at the minimum pipe window; entering the mode need not withhold useful bytes. First locate actual cwnd stalls and deadline @@ -40,6 +48,34 @@ harness in CI, with broader network cases at least nightly, and a verdict with pinned sources/configurations. Any adopted production policy gets a separate implementation quest; this study does not silently change defaults. +### Google differences + +The 2026-09-21 audit compared noq-proto 1.3.0 / upstream `1a26a8b` with +Google Linux BBRv3 `90210de4` and Google QUICHE `535a2730`. Recheck current +upstream sources before testing. Use QUICHE's actual `bbr3_sender.cc` with +its shared `bbr2_*` model; the older BBR2 sender is not a substitute for its +BBR3 state machine. + +- [Google Linux](https://github.com/google/bbr/blob/90210de4b779d40496dee0b89081780eeddf2a60/net/ipv4/tcp_bbr.c#L1924) + recognizes growth on any ACK and increments the plateau counter only at a + round boundary. Noq follows + [draft-06](https://www.ietf.org/archive/id/draft-ietf-ccwg-bbr-06.html#section-5.3.1.2)'s + earlier round-start gate. QUICHE instead checks its bandwidth maximum at + round boundaries. Test bursty and aggregated ACKs and changing bottleneck + capacity for premature plateau exits. +- [Google Linux](https://github.com/google/bbr/blob/90210de4b779d40496dee0b89081780eeddf2a60/net/ipv4/tcp_bbr.c#L1803) + and [Google QUICHE](https://github.com/google/quiche/blob/535a2730e77d47e0dc03746555cc9c34b17bc9e9/quiche/quic/core/congestion_control/bbr3_sender.cc#L1079) + stop precautionarily when probing reaches a previously lossy inflight + bound, then accelerate a later probe if feedback is clean. Noq lacks this + state and transition. Compare shallow queues, capacity increases, random + loss, and competing flows. + +Report throughput, queue delay, loss, convergence time, and fairness with +pinned code and configurations. Keep transport differences and QUICHE flags +explicit and compare each algorithm change separately. A simulation result +is not an end-to-end network measurement. + ## Related +- [Upstream the fork](/quest/m1/quic/upstream.md) - share useful findings with upstream - [Discover media headroom](/quest/m2/quic-probe.md) - preserving an estimate and discovering spare capacity are separate problems diff --git a/quest/m2/quic-deadline.md b/quest/m2/quic-deadline.md index 09e0efa7a5..008fcbf9c2 100644 --- a/quest/m2/quic-deadline.md +++ b/quest/m2/quic-deadline.md @@ -42,6 +42,10 @@ retransmitted past their deadline (must be zero), spurious resets under reordering, and probe overhead versus the default PTO. A proactive probe that raises loss or latency under any profile stays off by default. +Decided in the 2026-09-30 audit: moved to m2. No m1 quest consumes it, and +[Upstream the fork](/quest/m1/quic/upstream.md) offers it when it lands +rather than waiting on it. + ## Required - [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) - the @@ -49,5 +53,7 @@ raises loss or latency under any profile stays off by default. ## Related +- [Receive timestamps](/quest/m3/quic-receive-ts.md) - a measured forward + delay replaces the half-RTT estimate - [Discover media headroom](/quest/m2/quic-probe.md) - can reuse retransmission machinery if redundant capacity probes prove worthwhile diff --git a/quest/m2/quic-ecn-measure.md b/quest/m2/quic-ecn-measure.md index 2745829780..b04caf4481 100644 --- a/quest/m2/quic-ecn-measure.md +++ b/quest/m2/quic-ecn-measure.md @@ -36,3 +36,6 @@ from 1.3.1 is a defective baseline, not evidence that classic ECN cannot help. and the tcpdump summaries beside the numbers in the L4S quest's Plan. If neither provider preserves the marks, say so there: L4S stays off and the marking response is only a lab result. + +Decided in the 2026-09-30 audit: moved to m2 with its only consumer, +[L4S on the backbone](/quest/m2/quic-ecn.md). diff --git a/quest/m2/quic-ecn.md b/quest/m2/quic-ecn.md index 7040a52394..a2779e7c03 100644 --- a/quest/m2/quic-ecn.md +++ b/quest/m2/quic-ecn.md @@ -32,3 +32,8 @@ need the fork. - ECN visibility stays on the wire: the study observes marks with tcpdump, and exposing per-path ECN state in stats is a later quest if operators need it. + +## Required + +- [Measure ECN on the backbone](/quest/m2/quic-ecn-measure.md) - the + provider verdict this quest acts on diff --git a/quest/m2/quic-egress-profile.md b/quest/m2/quic-egress-profile.md index 6ab4bc88f2..6a30e7127f 100644 --- a/quest/m2/quic-egress-profile.md +++ b/quest/m2/quic-egress-profile.md @@ -1,23 +1,56 @@ -# [S] Send batching across connections +# [M] Relay egress syscall and allocation profile ## Goal -A measured verdict on batching sends across connections. The tokio path sends -one `sendmsg` per GSO train per connection; a worker flushing many small -connections pays a syscall each. `sendmmsg` submits every ready train in one -call. The io_uring path already queues one SQE per train and submits them -together, so the question there is whether it already gets the benefit. +A measured verdict on the relay's egress costs below moq-net: syscalls per +train, allocations and copies per frame, and whether a released GSO train +bursts at the bottleneck. Each lever below is built only if the profile +shows its cost; a lever measured within noise is recorded and closed. ## Plan -- Add `sendmmsg` to `moq-noq-udp` behind the existing GSO path: the runtime - collects the trains ready across connections in one poll and submits them - together, falling back to per-train `sendmsg` on partial failure the way - the GSO fallback does. -- Measure syscalls per second and CPU per Gbps on the chat and fanout shapes - with many connections, tokio workers versus io_uring workers, before and - after. Confirm from the io_uring worker's `enters` counter that the ring - already amortizes the same way. - -A measured no-win abandons the tokio change; the io_uring result is recorded -either way. +Decided in the 2026-09-30 audit: send batching, kernel pacing, and send +buffer pools merged into this one measure-first quest, since all three are +questions about the same egress path and share one profile. + +Measure first, on the chat and fanout shapes with many connections, tokio +workers versus io_uring workers, with the existing profiling captures: + +- Syscalls: the tokio path sends one `sendmsg` per GSO train per connection; + the io_uring path queues one SQE per train and submits them together. Count + syscalls per second and CPU per Gbps, and confirm from the io_uring + worker's `enters` counter that the ring already amortizes. +- Allocations: noq's `SendBuffer` keeps a write above 1452 bytes as the + caller's `Bytes` and coalesces smaller writes into a `BytesMut`. Count + allocations and bytes copied per frame, split by frame size, so the share + of sub-threshold copies is known. +- Bursts: noq paces inside `poll_transmit` on both runtimes (#4400, + rs/moq-uring/src/quic/noq/connection.rs:814-817), so the only open question + is whether a released GSO train leaves the NIC as a burst the bottleneck + cannot absorb. On a netem bottleneck, compare inter-packet gaps and queue + occupancy for a 64-segment train against the same bytes paced at the + controller's rate. + +Then, only where the profile shows a cost: + +- `sendmmsg` in `moq-noq-udp` behind the existing GSO path, submitting every + train ready across connections in one call, falling back to per-train + `sendmsg` on partial failure the way the GSO fallback does. +- A `BufFactory`-style seam on `SendBuffer` in the fork that takes buffers + from a pool sized from the send window and returns them on ACK. +- `SCM_TXTIME` stamps, one per train, at the time noq's pacer would have + released it, tested under both `etf` and `fq`. The socket is shared across + connections per worker, so `SO_MAX_PACING_RATE` cannot express + per-connection pacing. Measure on a real NIC as well as loopback, since + `etf` needs hardware offload to be exact. + +Report CPU per Gbps, RSS, loss, and p99 latency via `just bench BASE` on +Linux. The verdict names which levers ship and which qdisc, if any, relay +hosts need. + +## Related + +- [#3201](/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - + zero-copy on the same trains +- [#3204](/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md) - + the UDP-side pool the same buffers could feed diff --git a/quest/m2/quic-keep-alive.md b/quest/m2/quic-keep-alive.md index f4ebdf6530..7c7fc08512 100644 --- a/quest/m2/quic-keep-alive.md +++ b/quest/m2/quic-keep-alive.md @@ -5,7 +5,9 @@ A connection sends a PING only when its idle deadline is near, never on a fixed clock. An idle connection with a 30 s idle timeout costs one packet per roughly 30 s minus a few PTOs; a busy connection costs none. The -`keep_alive` interval knob on `quic::Client` and `quic::Server` goes away. +`keep_alive` setting on `quic::Client` and `quic::Server` becomes an optional +maximum, default none. Redefining it is a config and CLI break, so this +targets `dev`. ## Plan @@ -24,9 +26,9 @@ timeout by hand. Replace it in the fork: shorter life than the idle timeout. That is the only knob. - The multipath per-path keep-alive follows the same rule per path. -moq-tokio drops `keep_alive` from the `[quic]` sections, CLI flags, and env -vars; NAT-sensitive deployments keep it as the new maximum, documented in -`doc/bin/relay/config.md`. The qmux WebSocket keep-alive (`qmux::ws::KeepAlive`, +moq-tokio's `quic.keep_alive` (`rs/moq-tokio/src/quic.rs`) in the `[quic]` +sections, CLI flags, and env vars becomes that optional maximum, default none, +documented in `doc/bin/relay/config.md` for NAT-sensitive deployments. The qmux WebSocket keep-alive (`qmux::ws::KeepAlive`, 5 s ping and 30 s deadline) already has this shape; make its wording match. Tests: an idle connection survives an idle timeout with exactly one PING per diff --git a/quest/m2/quic-probe.md b/quest/m2/quic-probe.md index 85ca7e8c63..9f9f4827d1 100644 --- a/quest/m2/quic-probe.md +++ b/quest/m2/quic-probe.md @@ -47,3 +47,4 @@ retain the baseline and record why before exposing an ineffective option. ## Related - [Natural media drains](/quest/m2/quic-bbr-natural-drain.md) - separate ProbeRTT policy experiment +- [GCC egress experiment](/quest/m3/quic-gcc.md) - delay control changes what headroom means diff --git a/quest/m2/quic-qmux.md b/quest/m2/quic-qmux.md index c67682ead8..30bde05913 100644 --- a/quest/m2/quic-qmux.md +++ b/quest/m2/quic-qmux.md @@ -38,9 +38,8 @@ Add the missing wire evidence before release: golden draft-02 vectors, bidirectional interoperability against the published `qmux` 0.5.x crate, and the TypeScript qmux/WebSocket peer used by `js/net`. Preserve rejection of prohibited QUIC frames, params-first setup, record-size validation, close and -reset semantics (the first recorded close wins, per -[close codes](/quest/m1/close-codes.md)), keep-alive behavior, and bounded -flow-control tests. +reset semantics (the first recorded close wins, as close codes #4262 +settled on dev), keep-alive behavior, and bounded flow-control tests. The crate lives in the fork's workspace as `moq-noq-qmux`, so the stream state machine internals it drives stay crate-private there; `moq-dev/web-transport`'s @@ -48,6 +47,16 @@ state machine internals it drives stay crate-private there; `moq-dev/web-transpo state machine in the dependency graph, and the accessors qmux needs are `pub(crate)` to that workspace, never part of `moq-noq-proto`'s public API. +Release it from the fork like the other fork crates: tag `moq-noq-qmux` with +the parent noq commit and the carried patches (each with its upstream PR or +the reason there is none), pin the crates.io version in this workspace, and +verify minimal, default, and all-feature builds so enabling iroh, qmux, or +the uring runtime never unifies two copies of the stream state. The quest +completes when that release is on crates.io and `Cargo.lock` names it. + +Decided in the 2026-09-30 audit: moved to m2, since no m1 quest consumes it. +The separate QUIC release quest was deleted and its qmux step folded here. + ## Required - [Reliable stream reset](/quest/m1/quic/reliable-reset.md) - qmux reuses the diff --git a/quest/m2/relay-io-uring-package.md b/quest/m2/relay-io-uring-package.md index 23dc0e46d9..7e82c06e82 100644 --- a/quest/m2/relay-io-uring-package.md +++ b/quest/m2/relay-io-uring-package.md @@ -38,4 +38,6 @@ Public API: none. Wire: none. - [Flow-control windows](/quest/m1/uring-flow-control-windows.md) - the `[quic]` section must not be refused at startup on the ring - [Close before teardown](/quest/m1/quic/uring-close.md) - sessions on the - ring must end with their application close + ring must end with their application close; probably fixed by + [#4431](https://github.com/moq-dev/moq/pull/4431), so re-run its repro + before starting and drop this blocker if it passes diff --git a/quest/m2/rs2ts-ietf.md b/quest/m2/rs2ts-ietf.md index edadfeaa27..75390d156e 100644 --- a/quest/m2/rs2ts-ietf.md +++ b/quest/m2/rs2ts-ietf.md @@ -14,6 +14,10 @@ they stay exact as `U64` and only fail where code converts them to Public API: breaks `@moq/net`; retargets to `dev`. Wire: none. +Decided in the 2026-09-30 audit: deferred to m2 until generated lite passes +its no-downgrade go/no-go in the [rs2ts line](/quest/m1/rs2ts/README.md). + ## Required - [Generated lite](/quest/m1/rs2ts/lite.md) - the pipeline this reuses +- [Sans-IO IETF session](/quest/m2/rs2ts-sans-io-ietf.md) - the session shape it translates diff --git a/quest/m2/rs2ts-sans-io-ietf.md b/quest/m2/rs2ts-sans-io-ietf.md index 542c1965a1..a6d764f4e3 100644 --- a/quest/m2/rs2ts-sans-io-ietf.md +++ b/quest/m2/rs2ts-sans-io-ietf.md @@ -16,6 +16,9 @@ first with the IETF session behind it, move the session out. Public API: breaks moq-net's IETF session API; retargets to `dev`. Wire: none. +Decided in the 2026-09-30 audit: deferred to m2 with generated IETF, its only +consumer, until generated lite passes its go/no-go. + ## Required - [Sans-IO lite session](/quest/m1/rs2ts/sans-io/lite.md) - sets the driver shape diff --git a/quest/m2/sei.md b/quest/m2/sei.md index a796c5bf38..369c8d583b 100644 --- a/quest/m2/sei.md +++ b/quest/m2/sei.md @@ -1,24 +1,39 @@ -# SEI separation evaluation +# [S] Measure whether SEI separation is worthwhile ## Goal -Keep H.264 and HEVC SEI inline unless measured savings or a concrete -metadata-only consumer justify separate delivery. Evaluate that tradeoff before -committing a catalog, marker, or reassembly format. +Record enough evidence to decide whether a separate SEI track is worth its +framing and reassembly cost. Keep H.264 and HEVC SEI inline while this is +unresolved; a no-go verdict is a valid outcome. ## Plan -SEI separation is deferred. Existing metadata consumers may inspect the inline -bitstream; this line does not block captions or unrelated timed-metadata -carriage. The schema and implementation quests are conditional follow-ons, -not permission to strip by default. A no-go verdict abandons them. +Decided in the 2026-09-30 audit: collapse the line into this study. The +schema, Rust, and web reinsertion quests were deleted; they only make sense +after a positive verdict, and would be re-scoped from it. -Preserve decoder, display, recovery-point, and caption behavior. Raw vendor -payloads may be large, but that does not establish typical bandwidth savings. -Total storage and bytes delivered to a video-only subscriber are different -measurements. Any future split must state which payloads move and how missing -metadata is handled; do not promise byte-faithful export after a deadline miss. +Measure SEI payload types, bytes, and cadence on representative H.264 and HEVC +inputs. Separate small timing/display metadata, captions, encoder information, +and arbitrary vendor data. Identify a concrete metadata-only consumer if one +motivates the feature. Do not infer typical savings from a synthetic large +payload or from the codec permitting one. + +Compare current inline delivery with the bytes a video-only subscriber could +avoid, including any marker/sidecar overhead. Report total storage separately: +putting the same bytes in two tracks does not itself reduce a complete archive. + +Account for recovery points, display metadata, captions, and unknown payloads; +identify what must remain inline or be restored for each supported receiver. +A deadline bounds waiting but cannot prove absent metadata never existed. +Include loss, late arrival, and consumer compatibility in the tradeoff. + +Return a recommendation for maintainer agreement. A positive verdict scopes +which payloads move, whether extraction is opt-in, and the association and +latency contract, then files the follow-on quests. A negative verdict ends the +line without affecting captions or unrelated timed-metadata carriage. ## Related -- [Colour model](/quest/m1/color-model.md) - preserves display metadata semantics +- [Catalog colour model](/quest/m2/color-catalog.md) - preserves display metadata semantics +- [fMP4 emsg](/quest/m2/emsg.md) - independently settles carriage for metadata already outside video +- [CEA-608/708](/quest/m2/captions-cea.md) - can read inline caption SEI without waiting for this experiment diff --git a/quest/m2/signed-priority.md b/quest/m2/signed-priority.md index 9fcfa54d10..5f2136fb72 100644 --- a/quest/m2/signed-priority.md +++ b/quest/m2/signed-priority.md @@ -44,6 +44,10 @@ moq-archive's `Info::priority` follows. Its version-1 `.info` stores the Report the wire impact in the PR: none in format, but the default byte moves again, from 127 to 128 on moq-lite and from 128 to 127 on IETF. +Decided in the 2026-09-30 audit: moved to m2. It stays deferred unless it +ships in the same `dev` release as the moxygen default change, so the default +byte moves once instead of twice. + ## Required - [Moxygen compatibility](/quest/m1/moxygen/README.md) - ships the 127 default and the one-urgency invariant this re-maps @@ -51,3 +55,4 @@ again, from 127 to 128 on moq-lite and from 128 to 127 on IETF. ## Related - [Scope track priority](/quest/m1/track-priority-scope.md) - which streams a priority competes with, not its type +- [Hierarchical stream scheduling](/quest/m1/quic/scheduler.md) - reworks the same priority arithmetic in the transport diff --git a/quest/m2/starvation-frames.md b/quest/m2/starvation-frames.md index 610cce625a..bb71aa2722 100644 --- a/quest/m2/starvation-frames.md +++ b/quest/m2/starvation-frames.md @@ -41,6 +41,10 @@ Backends that return unsupported from `poll_acked` keep the group-granularity sampling from the parent quest, so the histogram never disappears over qmux or a browser transport; document which resolution a node offers. +Decided in the 2026-09-30 audit: moved to m2. The group-granularity sampler +from the parent quest answers the m1 question, and this waits on the m2 ACK +hook. + Tests: the frontier tracking frame ends under a peer that acknowledges in bursts, with interval samples landing one bucket lower than group-granularity tracking of the same run; a reset mid-group attributing only the @@ -52,3 +56,5 @@ unsupported. - [Starvation](/quest/m1/qos/starvation.md) - fixes the wire shape and the group-granularity fallback +- [poll_acked in web-transport](/quest/m2/quic-ack-hook.md) - the released + hook this samples through diff --git a/quest/m2/stats-delta.md b/quest/m2/stats-delta.md index f53b5aefaf..04e757a8af 100644 --- a/quest/m2/stats-delta.md +++ b/quest/m2/stats-delta.md @@ -106,6 +106,11 @@ maintainer's call; ask before writing one. Public API impact: additive on moq-stats unless the helpers change. Wire impact: new on-demand tracks; existing tracks unchanged. +## Required + +- [Bench coverage](/quest/m2/bench-coverage.md) - its stats producer benchmark is the moq-json snapshot encoder profile the gate needs + ## Related - [Stats format page](/doc/concept/stats.md) - where the new flavor is documented +- [Compressed tracks](/quest/m2/flate.md) - group-scoped DEFLATE tracks, whose group-window discipline this flavor repeats diff --git a/quest/m2/stats-encoder-feedback.md b/quest/m2/stats-encoder-feedback.md index ca232eb288..7a160a3e94 100644 --- a/quest/m2/stats-encoder-feedback.md +++ b/quest/m2/stats-encoder-feedback.md @@ -10,6 +10,9 @@ it already follows. Keyframe requests stay out. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer; the media stats it +reads land first in m1. + - `encode::Options` in `rs/moq-video` and `rs/moq-audio` (the producer options, beside `bandwidth`) gains `echo: Option`. - The handle is built from an `origin::Consumer` and the echo path, @@ -56,6 +59,23 @@ it already follows. Keyframe requests stay out. - Document the flag in `doc/bin/cli.md` and the loop and its config in the moq-video README. +Open, to settle before starting (moved from the +[media stats](/quest/m1/stats/README.md) line): + +- **Referenced-rendition feedback.** A derivative catalog (a `moq-transcode` + passthrough) collects feedback for a source rendition it lists, but owns + no encoder for it, and the source encoder reads only its own catalog's + prefix. Candidates: the derivative forwards those rows to the source's + echo path, or the source encoder also reads catalogs that reference it, + or referenced renditions stay report-only. +- **Shared echo prefixes.** Two catalogs can resolve their echo paths to one + prefix (`../viewers` from `room/a/live` and `room/b/live`). Then a viewer + using one name for both closes one `.echo` with the other, and each + publisher reads the other's reports under a shared alias. Candidates: + require each catalog's echo prefix to be its own, as application policy + like the token rights, or carry the catalog's broadcast in the snapshot + and ignore reports for another. + ## Required - [Schema](/quest/m1/stats/schema.md) - the `echo` section and feedback snapshot @@ -66,3 +86,5 @@ it already follows. Keyframe requests stay out. - [Audio follows the grant](/quest/m1/2848-follow-the-bandwidth-grant-in-moq-audio-instead-of.md) - the audio rate follow this signal would feed +- [Ladder](/quest/m2/ladder/README.md) - the transcode ladder that adapts to + its uplink today diff --git a/quest/m2/teleop/README.md b/quest/m2/teleop/README.md index 2f125ee661..c9deab559e 100644 --- a/quest/m2/teleop/README.md +++ b/quest/m2/teleop/README.md @@ -5,12 +5,16 @@ MoQ carries a robot's video down and its control up on one session, as a library capability rather than a demo convention. -Generic in the library, integration-shaped in the proofs: Kyber is the first -transport replacement, while ArduPilot remains the first open protocol and -flight integration. The primitive is what any teleoperated machine needs. The -Kyber integration itself (replacing Kymux with moq-net and hang for Kyber's -media, input, feedback, and control) lives in Kyber's own repository, so it -has no quests here. +Generic in the library: the primitive is what any teleoperated machine needs, +and integrations are adapters on top of it. ArduPilot is the first open +protocol and flight integration, parked in m3 until a real ArduPilot user or +partner shows up. + +Decided in the 2026-09-30 audit: the ROS 2 bridge was deleted, cross-track +correlation folded into [robot](/quest/m2/teleop/robot.md), V4L2-M2M encoding +folded into [CLI packaging](/quest/m1/cli-packaging.md), and the MAVLink +bridge, SITL proof, and browser package moved to m3. Kyber is a competitor +with proprietary framing, not a transport we replace. ## Plan @@ -25,9 +29,9 @@ demo and private to it. Every integrator rebuilds the announce fan-in, the operator arbitration, and the latency instrumentation from scratch. The hang catalog is no longer a gap: it advertises data tracks in its `json` -and `binary` sections beside video and audio. What is genuinely missing is -`moq-video`'s V4L2-M2M encoder backend in a released `moq-cli`, so the boards -that fly have no native hardware-encode path anyone can install. +and `binary` sections beside video and audio. The other missing piece, +`moq-video`'s V4L2-M2M encoder in a released `moq-cli`, is tracked by +[CLI packaging](/quest/m1/cli-packaging.md). ### Two delivery classes, one session @@ -97,3 +101,13 @@ stating plainly because it is what a builder is comparing against. extends those types rather than adding a second stats surface - [Video hardware validation](/quest/m3/video-hardware.md) - the VAAPI run that covers Intel ground robots and NUC companions +- [CLI packaging](/quest/m1/cli-packaging.md) - ships the V4L2-M2M encoder the + boards that fly need +- [MAVLink bridge](/quest/m3/teleop-mavlink.md) - a `moq-mavlink` gateway, + parked until a real ArduPilot user or partner +- [SITL proof and browser ground station](/quest/m3/teleop-proof.md) - ArduPilot + SITL flown from a browser, parked with the bridge +- [Browser teleoperation package](/quest/m3/teleop-browser-package.md) - + `@moq/robot` mirroring the Rust crate, parked with the bridge +- [Text schema](/quest/m2/text-schema.md) - non-media tracks in a catalog, + arrived at from the media side diff --git a/quest/m2/teleop/robot.md b/quest/m2/teleop/robot.md index f88dd40f8b..dd7d317ff0 100644 --- a/quest/m2/teleop/robot.md +++ b/quest/m2/teleop/robot.md @@ -82,6 +82,28 @@ The framing is where the guarantee lives, not the subscription flags: roughly 100 ms of a 130 ms glass-to-glass total, so we would mostly be measuring somebody's webcam. +### Cross-track correlation + +Decided in the 2026-09-30 audit: correlation folds in here rather than being +its own quest. A command, the telemetry sample it produced, and the video frame +showing the result share one timebase, so a recording is usable as training +data and an operator sees what the machine actually saw. + +The bridge is each broadcast's fixed catalog-root `clock: { wall, timescale }`, +documented in `doc/concept/hang.md` (#4461). Media and command tracks keep +their own timescales; convert PTS explicitly into the broadcast clock before +joining samples. The robot's video and telemetry share a clock; the operator's +command broadcast supplies its own mapping. + +The clock assumption: the two hosts' wall clocks are synchronized by the +deployment, not by the library. State this beside the API, since a join across +unsynchronized hosts looks valid while being wrong. Report whether a mapping is +present, but never infer synchronization from its presence. No per-record +anchors or clock-sync mechanism; see +[#2278](https://github.com/moq-dev/moq/issues/2278). + +### Port + Port `moq-boy` onto the crate in the same change, as the no-arbitration case. It is the only existing consumer, and if the abstraction cannot express crowd control then it is the wrong abstraction. diff --git a/quest/m2/text-schema.md b/quest/m2/text-schema.md index d4fed13360..b7fd16f6c0 100644 --- a/quest/m2/text-schema.md +++ b/quest/m2/text-schema.md @@ -8,6 +8,8 @@ the audio or video timeline it transcribes. ## Plan +Deferred to m2 in the 2026-09-30 audit: no named consumer. + The `text` catalog section already carries the rest of the contract: relative `broadcast` references so a transcription can live in its own broadcast, cue timing on the shared media clock, and `jitter` for the publisher's flush diff --git a/quest/m2/uring-open-contract.md b/quest/m2/uring-open-contract.md index 0acd060717..39e37fa4c5 100644 --- a/quest/m2/uring-open-contract.md +++ b/quest/m2/uring-open-contract.md @@ -18,3 +18,11 @@ Update the implementation quest with the selected state transitions, resource bounds, cancellation behavior, and regression cases for concurrent openers, credit starvation, dropped futures, and finish/drop. This quest ships the plan; it does not close #3129 or implement an unsettled public contract. + +Decided in the 2026-09-30 audit: moved to m2 with #3129, which it plans. +io_uring ships in no package, and #3129 fixes an API wart no in-tree caller +hits. + +## Related + +- [Write headers at open](/quest/m2/3129-moq-uring-write-the-webtransport-stream-header-at-open.md) - implementation after the contract is settled diff --git a/quest/m2/uring-tcp/README.md b/quest/m2/uring-tcp/README.md index 8be87155ee..831e86655f 100644 --- a/quest/m2/uring-tcp/README.md +++ b/quest/m2/uring-tcp/README.md @@ -19,10 +19,10 @@ The three quests below ship together as one capability, in order. The prerequisite that shapes the middle quest: **qmux sessions arrive through the axum router**. `web.rs` routes `/` and `/{*path}` to -`websocket::serve_ws` (rs/moq-relay/src/web.rs:256-259). The gate is +`websocket::serve_ws` (rs/moq-relay/src/web.rs:343-344). The gate is moq-relay's own `websocket` feature (rs/moq-relay/Cargo.toml:43, which is what turns on `axum/ws`) plus the runtime `resolved_ws()` check -(web.rs:257), so a WebSocket session is an HTTP upgrade before it is a media +(web.rs:342), so a WebSocket session is an HTTP upgrade before it is a media session. There is no moving qmux onto the ring without also running the HTTP server that upgrades it there. That is not a reason to rewrite axum: hyper is runtime-agnostic, so implementing `hyper::rt::{Read, Write, Executor}` over @@ -33,6 +33,11 @@ Measure before porting, the same way the echo bench (rs/moq-uring/benches/session_lite.rs) gated the UDP path. The ablation's number is what justifies the rest of the line. +Decided in the 2026-09-30 audit: moved to m2. The +[transport upgrade](/quest/m1/transport-upgrade/README.md) shrinks the +WebSocket hot path by moving clients to QUIC, and no fleet demand asks for +ring TCP. + ## Required - [Ablation](/quest/m2/uring-tcp/ablation.md) - measure ring TCP against tokio diff --git a/quest/m2/video-codec-coverage.md b/quest/m2/video-codec-coverage.md index 7b207a06f6..423619c9ca 100644 --- a/quest/m2/video-codec-coverage.md +++ b/quest/m2/video-codec-coverage.md @@ -31,3 +31,4 @@ Public API and wire: no changes during this study. - [NVIDIA formats](/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md) - existing AV1 encode and 10-bit scope - [VP8/VP9](/quest/m1/obs-moq-video/vpx.md) - existing portable decoder scope +- [VAAPI](/quest/m2/video-vaapi.md) - existing Linux codec expansion diff --git a/quest/m2/video-vaapi.md b/quest/m2/video-vaapi.md index e2bc947fce..eb95c2e0a8 100644 --- a/quest/m2/video-vaapi.md +++ b/quest/m2/video-vaapi.md @@ -4,12 +4,17 @@ VAAPI encodes H.264 and H.265 from a DMA-BUF without a download, decodes H.265 as well as H.264, and the `vaapi` feature costs a consumer nothing at -build time so it can return to default-on. Every piece needs a `moq-dev/vaapi` -release first. +build time so it can return to default-on. A resize reuses its output +surfaces. ## Plan -Three gaps, one external dependency. +Decided in the 2026-09-30 audit: moved from m4, and the "gated on a +`moq-dev/vaapi` release" blocker is gone. That crate is our own repo (last +release 0.1.0 on 2026-09-24), so this quest includes the crate work (HEVC +encode, H.265 decode, pre-generated bindings, the resize pool) and cutting the +release, then bumping the workspace requirement here. The resize-pool quest +folds in for the same reason. **Decode.** The H.264 decoder landed (moq-vaapi 0.0.4, `decode/backend/vaapi.rs`), with the default `decode::Config::output` of `Output::Native` handing out @@ -39,12 +44,15 @@ fixed-width types, `c_char` left symbolic), so one checked-in file serves every Linux target, as `moq-v4l` already does for `videodev2.h`. Then the feature can return to default-on here. +**Resize pool.** moq-vaapi's `Processor` allocates the blit output with +`ExportedFrame::from_surface` on every resize. Keep one surface per output +size; when the exported frame drops, the surface returns for the next blit of +that size. A frame the consumer still holds is not overwritten; the processor +allocates another. Keep at most one free surface per size and destroy any +returned past that, the decoder pool's rule. `Surface::resize` stays the same +call. The reuse test belongs in moq-vaapi; here, confirm a resize still returns +an NV12 DMA-BUF. + Note what is already fine: a host with libva present but no usable VA driver already falls back cleanly, since `Encoder::new` returns `Err` and `backend::open` drops to openh264. - -## Required - -- A `moq-dev/vaapi` release exposing an HEVC encoder (H.264 decode is in - 0.0.4; DMA-BUF encode and VPP shipped in 0.1.0) and pre-generated bindings - instead of a bindgen build script diff --git a/quest/m2/x11-capture-shm.md b/quest/m2/x11-capture-shm.md index 46e53d3ca5..dbf4f8b984 100644 --- a/quest/m2/x11-capture-shm.md +++ b/quest/m2/x11-capture-shm.md @@ -24,5 +24,12 @@ capture does the same thing with a per-frame `get_geometry`; it already selects `StructureNotify` and drains that queue between frames, so `ConfigureNotify` is there to be consumed and the round trip dropped. -Both changes are contained to the one backend and are verifiable on a Linux +Decided in the 2026-09-30 audit: the X11 half of the frame-buffer quest folds +in here, since it is the same read path. `PixelFormat::rgb` allocates a +`w * h * 3` `Vec` per frame and fills it with three `push` calls per pixel, +then `I420::from_rgb` walks it again into a third buffer. Reuse an +`&mut Vec` the `Capture` owns and `clear()` it, so the allocation happens +once. Measure before and after rather than assuming. + +All of these changes are contained to the one backend and are verifiable on a Linux host with a real X session; CI compiles the file but cannot run it. diff --git a/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md b/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md index 5315677c57..53c502a3f5 100644 --- a/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md +++ b/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md @@ -35,6 +35,10 @@ The UDP path already assembles up to 64 KiB GSO trains in stable pool buffers, t Sweep the threshold across realistic chat and media packet trains. Record relay CPU, goodput, CQEs per send, copy-fallback rate, TX-pool pressure, p99 latency, and memory residency at fixed offered load. Enable it by default only where the end-to-end result beats regular `SendMsg`. +Decided in the 2026-09-30 audit: moved to m3. The #3224 prototype was 6 to +9% slower, the win needs a physical-NIC sweep nobody has run, and io_uring +ships in no package. + ## Closes - [#3201](https://github.com/moq-dev/moq/issues/3201) - close this issue when the quest finishes diff --git a/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md b/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md index 829281806d..05aa724c3d 100644 --- a/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md +++ b/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md @@ -34,6 +34,14 @@ The TX pool already owns stable `Box<[u8]>` allocations and grows lazily. If zer Compare #3201 with and without registered buffers using the same threshold and workload matrix. Record CPU, cycles, registration cost, locked memory, pool starvation, throughput, and latency. Do not add the complexity unless it improves the winning zero-copy range beyond ordinary `SendMsgZc`. +Decided in the 2026-09-30 audit: moved to m3. Fixed buffers on +`SENDMSG_ZC` need Linux 6.15, above the 6.12 floor, and the #3201 +experiment it builds on moved to m3 too. + +## Required + +- [#3201](/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - the zero-copy send experiment this extends + ## Closes - [#3204](https://github.com/moq-dev/moq/issues/3204) - close this issue when the quest finishes diff --git a/quest/m3/README.md b/quest/m3/README.md index 1c130ea9b7..4777061d72 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -3,18 +3,21 @@ ## Goal Work whose first step is outside this repository: hardware nobody on the team -has, a partner or customer, or a hosting provider's offer. Nothing here can -start by opening an editor. +has, a partner or customer who asks for it, or a hosting provider's offer. +Nothing here should start by opening an editor. ## Plan A quest lands here when its gate is the outside world, not its priority. Each states the condition in prose or as a plain-text `Required` bullet. When the -condition clears, move the quest to the milestone its work belongs in. +condition clears, move the quest to the milestone its work belongs in. A +speculative feature with no consumer parks here rather than in m2, and is +deleted when it goes stale; git history keeps it. ## Required -- [Video hardware validation](/quest/m3/video-hardware.md) - run the encode, capture, and zero-copy paths that were written but never run on real machines +- [Video hardware validation](/quest/m3/video-hardware.md) - run the encode, capture, and zero-copy paths that were never run on real machines, including PipeWire on KDE, the camera portal, and a Pi +- [NVENC AV1](/quest/m3/nvenc-av1.md) - AV1 encode through NVENC, once an Ada-generation GPU is available - [Embedded video](/quest/m3/video-embedded.md) - EGL import in the renderer, so moq-video presents on a Pi - [#3201: moq-uring: use SENDMSG_ZC for large UDP GSO trains](/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md) - complete the prerequisite issue first - [#3204](/quest/m3/3204-moq-uring-register-tx-pool-buffers-for-zero-copy-sends.md) - moq-uring: register TX-pool buffers for zero-copy sends diff --git a/quest/m3/af-xdp.md b/quest/m3/af-xdp.md index 7a521fa67d..080d3d54dd 100644 --- a/quest/m3/af-xdp.md +++ b/quest/m3/af-xdp.md @@ -6,7 +6,7 @@ A measured verdict on an AF_XDP socket as the io_uring worker's UDP path on the hosts the fleet runs today. virtio-net supports AF_XDP in copy mode on any Linode or OVH instance and zero-copy where the driver allows; if it lifts the per-worker packet ceiling or cuts CPU per Gbps materially against the -io_uring path, the DPDK question becomes concrete; if not, kernel bypass is +io_uring path, full kernel bypass becomes a concrete question; if not, it is closed until hardware changes. ## Plan @@ -17,4 +17,8 @@ closed until hardware changes. behind a feature; nothing ships. - Measure packets per second, CPU per Gbps, and p99 latency on one virtio host in copy mode, on the chat and fanout shapes, against the io_uring path - with the zero-copy and busy-poll quests' best settings. + with the zero-copy quests' best settings. + +Decided in the 2026-09-30 audit: moved to m3. The relay packages don't ship +io_uring yet, so a bypass that competes with it has no deployment to +improve. diff --git a/quest/m3/cat/README.md b/quest/m3/cat/README.md index 6068a26f90..4087147953 100644 --- a/quest/m3/cat/README.md +++ b/quest/m3/cat/README.md @@ -41,6 +41,10 @@ Boundaries decided while planning: ## Plan +In m3 because nobody is asking for CAT yet and the claim keys are +unregistered; a consumer presenting CATs, or the draft registering the keys, +brings it back. + Order: the SETUP option already reaches the auth server as `moq_auth::Request.token`; verification comes first, then our clients present one. Everything rides `moq_auth::Request` and @@ -49,6 +53,7 @@ one. Everything rides `moq_auth::Request` and ## Required +- draft-ietf-moq-c4m registers the moqt claim keys - [Verify](/quest/m3/cat/verify.md) - `moq_auth::cat` turns a CAT into a grant and `moq auth serve` admits one; `moq auth sign|verify` mint and check the format diff --git a/quest/m3/cat/present.md b/quest/m3/cat/present.md index fcdc6f9551..53b2a8f877 100644 --- a/quest/m3/cat/present.md +++ b/quest/m3/cat/present.md @@ -13,9 +13,9 @@ the option fails loud instead of dropping the credential. - [Token in band](/quest/m1/auth/token-in-band.md) defines the client token configuration and writes the first configured token into the setup option - as type 0. This quest makes the kind explicit: each configured token is a - `moq_net::setup::Token { kind, value }`, a JWT keeps kind `0x0` and its - URL and AUTH stream behavior, and a CAT is kind `0x01`. `--connect-token` + as type 0. `moq_net::setup::Token { kind, value }` already carries the + kind, with CAT = `0x1` (#4278); a JWT keeps kind `0x0` and its URL and + AUTH stream behavior. `--connect-token` keeps taking a JWT; `--connect-cat ` (and `MOQ_CONNECT_CAT`) adds a CAT. One CAT per connection: it is the connection credential, so a configured CAT takes the setup option and the JWT that would have gone diff --git a/quest/m3/cpp-conan.md b/quest/m3/cpp-conan.md index 19e36c2ac2..9d7048dc7b 100644 --- a/quest/m3/cpp-conan.md +++ b/quest/m3/cpp-conan.md @@ -9,6 +9,9 @@ Windows, macOS, and Linux. ## Plan +In m3 until a Conan consumer asks; vcpkg and the release tarball cover +C++ consumers first. + - A `moq-cpp` recipe, named after the package, on a moq-dev remote (Artifactory or a GitHub-hosted `conan` index) that packages the prebuilt release tarball per setting and exports the `moq::cpp` CMake target from diff --git a/quest/m3/cs/README.md b/quest/m3/cs/README.md index 2fcf1726c2..dca620bd43 100644 --- a/quest/m3/cs/README.md +++ b/quest/m3/cs/README.md @@ -10,6 +10,9 @@ a mirror. Unity is a separate prototype. ## Plan +In m3 until a .NET or Unity consumer asks; the C++ line covers native +embedders first. + NordSecurity's `uniffi-bindgen-cs` (latest `v0.11.0+v0.31.0`) already emits async methods as `Task` and async callback interfaces; it needs the same uniffi 0.32 port the Go, Dart, and C++ generators got. Plain .NET first; the @@ -25,3 +28,4 @@ up front. ## Related - [C++ through moq-ffi](/quest/m1/cpp/README.md) - the sibling line this copies +- [Unity prototype](/quest/m3/unity.md) - the package under IL2CPP diff --git a/quest/m3/hidden-exemption.md b/quest/m3/hidden-exemption.md index 18a993aca6..97dc8acac6 100644 --- a/quest/m3/hidden-exemption.md +++ b/quest/m3/hidden-exemption.md @@ -9,6 +9,9 @@ for peers that predate it. ## Plan +In m3 because it waits on a deployment, not on code; it comes back once +the mesh runs lite-07. + - Remove the `cluster_peer` argument from `connection::authorize` and its callers in `rs/moq-relay/src/{connection,uring,websocket}.rs`, and the forced `with_hidden(true)` on the outbound dial in `rs/moq-relay/src/cluster.rs`. @@ -17,4 +20,4 @@ for peers that predate it. ## Required -- Every deployed relay in the moq.pro mesh speaks a finalized moq-lite-07 or MoQ Hidden; `moq-lite-07-wip` is opt-in only. +- The moq.pro mesh deploys lite-07 diff --git a/quest/m3/intra-refresh-bindings.md b/quest/m3/intra-refresh-bindings.md index b6cb07cf5c..ea45a3093d 100644 --- a/quest/m3/intra-refresh-bindings.md +++ b/quest/m3/intra-refresh-bindings.md @@ -21,4 +21,5 @@ both modes. ## Required +- [Encode config](/quest/m3/intra-refresh-encode-config.md) - the core refresh variant this mirrors - [Codecs](/quest/m1/ffi-shape/codec.md) - the `MoqVideoGop` enum this extends diff --git a/quest/m3/intra-refresh-encode-config.md b/quest/m3/intra-refresh-encode-config.md index ab4f51ead7..14de81adb4 100644 --- a/quest/m3/intra-refresh-encode-config.md +++ b/quest/m3/intra-refresh-encode-config.md @@ -14,6 +14,11 @@ without replacing an API after 0.1. ## Plan +Decided in the 2026-09-30 audit: the encode side moved to m3 pending the +[GOP overhead](/quest/m2/gop-overhead.md) verdict. If a short keyframe GOP +is cheap, a flat bitrate is not worth encoder work; imported refresh streams +are still handled by [the m2 line](/quest/m2/intra-refresh/README.md). + - Extend the non-exhaustive `Gop` contract from main with refresh mode. Keep the settled frame-count units and `cut()` operation; do not replace the public config or rename the operation again. A cut in refresh mode asks @@ -43,4 +48,5 @@ without replacing an API after 0.1. ## Required +- [GOP overhead](/quest/m2/gop-overhead.md) - the verdict on whether keyframe cost justifies refresh encoding - [Catalog warmup](/quest/m1/catalog-warmup.md) - the field the producer publishes diff --git a/quest/m3/intra-refresh-nvenc.md b/quest/m3/intra-refresh-nvenc.md index f07c0d3492..e540a8c3e2 100644 --- a/quest/m3/intra-refresh-nvenc.md +++ b/quest/m3/intra-refresh-nvenc.md @@ -21,8 +21,16 @@ refuses the mode. producer's `warmup` matches the refreshed macroblocks, and translate a cut into a forced sweep restart instead of `FORCEIDR`. Check the capability at construction and refuse without it. -- Verification needs hardware: no CI runner has an NVIDIA GPU, so run the - probe by hand, feed the output through the H.264 and H.265 import quests' +- Verification needs hardware: run it on the [GPU CI](/quest/m1/gpu-ci.md) + host once it exists, else run the probe by hand, feed the output through the H.264 and H.265 import quests' splitters to confirm one group per sweep and the SEI count, and record the numbers in the PR. Any test that needs the GPU skips loudly rather than reporting success. + +## Required + +- [Encode config](/quest/m3/intra-refresh-encode-config.md) - the `Gop` enum and cut semantics this implements + +## Related + +- [GPU CI](/quest/m1/gpu-ci.md) - the RTX 3070 Ti runner that can verify this without a hand run diff --git a/quest/m3/intra-refresh-v4l2.md b/quest/m3/intra-refresh-v4l2.md index 6d2f5d9aa9..40e969ae20 100644 --- a/quest/m3/intra-refresh-v4l2.md +++ b/quest/m3/intra-refresh-v4l2.md @@ -31,3 +31,7 @@ claiming a boundary that was not encoded. - Verify on the hardware the backend already targets that the first sweep begins at frame zero and note whether the driver emits the SEI; the hang side does not need it. + +## Required + +- [Encode config](/quest/m3/intra-refresh-encode-config.md) - the `Gop` enum and cut semantics this implements diff --git a/quest/m3/livekit-shim.md b/quest/m3/livekit-shim.md index ba66c60475..bc3208d954 100644 --- a/quest/m3/livekit-shim.md +++ b/quest/m3/livekit-shim.md @@ -13,10 +13,14 @@ and the connect URL/token changed. ## Plan +In m3 until a LiveKit user asks to migrate; `@moq/room` already serves +new rooms directly. + - The shim is a LiveKit-API facade over `@moq/room`, which carries hang.live's convention: the room is a path prefix in the connection URL and token root, participants are discovered from the bare announce stream, identity is the - next path segment, and each participant publishes `/camera` - (camera + mic, hd/sd renditions) and `/screen` (screenshare, + path before the broadcast name, and each participant publishes + `/camera.hang` (camera + mic, hd/sd renditions) and + `/screen.hang` (screenshare, whose announce/unannounce is the screenshare lifecycle). The shim groups the two paths per identity into one RemoteParticipant and maps catalog entries to TrackPublications. @@ -27,6 +31,6 @@ and the connect URL/token changed. state derives from catalog track presence. Names and coarse state are a follow-up wired to the room SDK's `hang/*.json` metadata, not a rival scheme. -- Recommend tokens scoped to `put: /` so participants cannot - publish at each other's paths (hang.live grants `put` on the whole room - subtree today). +- Mint tokens with `@moq/room`'s `claims(room, identity)`, which scopes the + `publish` claim to the identity's subtree so participants cannot publish at + each other's paths. diff --git a/quest/m3/nvenc-av1.md b/quest/m3/nvenc-av1.md new file mode 100644 index 0000000000..1715d6ed32 --- /dev/null +++ b/quest/m3/nvenc-av1.md @@ -0,0 +1,31 @@ +# [M] NVENC AV1 encoding + +## Goal + +The NVENC backend encodes AV1 where the queried NVIDIA device and driver +support it, with OBU framing and an accurate catalog configuration through +transcode, and refuses it elsewhere. + +## Plan + +Split from the Main10 quest in the 2026-09-30 audit. It sits in m3 because it +is hardware-gated: AV1 NVENC needs an Ada GPU, and the only GPU CI host is an +RTX 3070 Ti. An Ada GPU on a CI host, or a consumer asking for AV1 encode, +brings it back. + +Query the capability at construction and refuse without it. Use existing +extensible codec enums; keep the NVIDIA backend optional and loaded at +runtime. Validate decoded pixels, profile, framing, drain, and refusal on +unsupported devices. Lack of suitable hardware leaves this unverified, not +complete. + +Public API: additive. Wire: existing AV1 codec signaling. + +## Required + +- Ada or newer NVIDIA GPU available for verification + +## Related + +- [Main10](/quest/m2/2147-moq-video-10-bit-hevc-and-av1-support-in-the-nvidia-codec.md) - the other half of #2147 +- [GPU CI](/quest/m1/gpu-ci.md) - the current runner lacks AV1 NVENC diff --git a/quest/m3/obs-linux-gpu.md b/quest/m3/obs-linux-gpu.md index 9b364d389a..2af3bc1b29 100644 --- a/quest/m3/obs-linux-gpu.md +++ b/quest/m3/obs-linux-gpu.md @@ -6,6 +6,9 @@ A supported Linux OBS graphics/encoder combination publishes composited video wi ## Plan +In m3 until the OBS encoder ships and a Linux user reports CPU readback as +the bottleneck. + - Start with an API feasibility probe. OBS exposes DMA-BUF import in `graphics.h`; that is not proof its compositor textures can be exported. Determine whether EGL/GL allocation export is available, or whether an upstream OBS hook or an encoder-owned exportable render target is required. - moq-video encodes a `Surface::DmaBuf` on VAAPI without a download, and scales one through VPP (moq-vaapi 0.1.0). This quest still has to turn an OBS compositor texture into a DMA-BUF that import accepts. - Negotiate DRM device, fourcc, plane offsets/strides, modifiers, and synchronization. Reuse `Surface::DmaBuf` and the hardware encoder's real import path, retaining allocation ownership until completion. A borrowed fd or an importable packed RGB texture does not prove the encoder accepts NV12 on the same device. @@ -20,3 +23,4 @@ A supported Linux OBS graphics/encoder combination publishes composited video wi ## Related - [Video hardware validation](/quest/m3/video-hardware.md) - native input and encoder acceptance need hardware evidence +- [VAAPI encode and decode](/quest/m2/video-vaapi.md) - H.265 and checked-in bindings. The DMA-BUF encoder import this quest needs is already on main (moq-vaapi 0.1.0). diff --git a/quest/m3/quic-gcc.md b/quest/m3/quic-gcc.md index ba049a17df..268726aa01 100644 --- a/quest/m3/quic-gcc.md +++ b/quest/m3/quic-gcc.md @@ -25,3 +25,13 @@ than loopback. State the experiment's boundary beside the result: netem cannot establish behavior against production cross traffic or real wifi and cellular loss. + +Receive timestamps are native-only: browsers never negotiate the extension, +so GCC can only target native peers or relay-to-relay sessions, not browser +egress. Decided in the 2026-09-30 audit: parked in m3 until such a consumer +exists. + +## Required + +- [Receive timestamps](/quest/m3/quic-receive-ts.md) - the per-packet + arrival times the delay filter runs on diff --git a/quest/m3/quic-receive-ts.md b/quest/m3/quic-receive-ts.md index b09d7c93f5..30b07a4f2c 100644 --- a/quest/m3/quic-receive-ts.md +++ b/quest/m3/quic-receive-ts.md @@ -25,3 +25,12 @@ written reason it does not pay. The GCC experiment requires this; a delay-based controller without per-packet arrival times is a different, weaker experiment. + +Both ends must be ours, so this is native-only and serves native peers or +relay-to-relay sessions. Decided in the 2026-09-30 audit: parked in m3 with +GCC until such a consumer exists. + +## Related + +- [QUIC GCC](/quest/m3/quic-gcc.md) - the controller that consumes it +- [Per-stream deadlines](/quest/m2/quic-deadline.md) - the other consumer diff --git a/quest/m3/routing-cost-domains.md b/quest/m3/routing-cost-domains.md index b22a39c42a..a21210a687 100644 --- a/quest/m3/routing-cost-domains.md +++ b/quest/m3/routing-cost-domains.md @@ -54,6 +54,10 @@ a second peer-policy mechanism. Completion is a documented decision, worked counterexamples or model checks, and independently completable follow-up quests. Open wire/API choices belong to this design exercise. +## Required + +- [Cluster routing](/quest/m1/cluster-routing/README.md) - this designs on its inter-cluster path vector + ## Related - [#3769](https://github.com/moq-dev/moq/pull/3769) - measurement-based pricing diff --git a/quest/m3/teleop-browser-package.md b/quest/m3/teleop-browser-package.md index 56e4c29449..ec5319449b 100644 --- a/quest/m3/teleop-browser-package.md +++ b/quest/m3/teleop-browser-package.md @@ -7,6 +7,8 @@ and delivery classes rather than reimplementing them. ## Plan +Parked in m3 until a real ArduPilot user or partner asks (2026-09-30 audit). + Follow the existing split: `net`, `hang`, `json` and `auth` each have a Rust crate and a TypeScript package, with zod schemas mirroring the Rust types. There is no `@moq/mux`, so the catalog extension goes through the same seam diff --git a/quest/m3/teleop-mavlink.md b/quest/m3/teleop-mavlink.md index aa50bb5853..ce96f5034a 100644 --- a/quest/m3/teleop-mavlink.md +++ b/quest/m3/teleop-mavlink.md @@ -8,6 +8,8 @@ and MAVProxy connect unchanged. ## Plan +Parked in m3 until a real ArduPilot user or partner asks (2026-09-30 audit). + ### Shape A library crate reached through `moq-cli`, the way `moq-srt` and `moq-rtmp` diff --git a/quest/m3/teleop-proof.md b/quest/m3/teleop-proof.md index b3fae97803..82a903937d 100644 --- a/quest/m3/teleop-proof.md +++ b/quest/m3/teleop-proof.md @@ -8,6 +8,8 @@ five minutes. ## Plan +Parked in m3 until a real ArduPilot user or partner asks (2026-09-30 audit). + The browser client subscribes video and telemetry and publishes control, the shape Blue Robotics' Cockpit proves is viable. It is the demo and the end-to-end proof of the primitive, not a bid to out-feature QGroundControl: @@ -20,3 +22,8 @@ somebody's webcam. Standing SITL up in CI is separate work with its own build dependencies. Reproducibility by hand is the bar here; automate it later if it proves worth the maintenance. + +## Required + +- [MAVLink bridge](/quest/m3/teleop-mavlink.md) +- [Browser teleoperation package](/quest/m3/teleop-browser-package.md) diff --git a/quest/m3/unity.md b/quest/m3/unity.md index 5defae3fce..3c3e42cd7c 100644 --- a/quest/m3/unity.md +++ b/quest/m3/unity.md @@ -10,6 +10,8 @@ target, subscribes to a broadcast, and plays decoded audio through an ## Plan +In m3 with the C# line; a Unity consumer asking brings both back. + - IL2CPP forbids dynamic callback marshaling: every reverse P/Invoke needs a static method with `[MonoPInvokeCallback]`. Audit what the generated `cs/ffi` emits for callback interfaces and futures, and whether the generator needs diff --git a/quest/m3/unreal.md b/quest/m3/unreal.md index 64b5912809..04df6f976a 100644 --- a/quest/m3/unreal.md +++ b/quest/m3/unreal.md @@ -10,6 +10,8 @@ editor stability across a play-stop-play cycle. ## Plan +In m3 until an Unreal consumer asks; the C++ package it links already exists. + - Consume the release tarball through the module's `Build.cs` (`PublicAdditionalLibraries`, include paths); Unreal's build does not use vcpkg or CMake, which is why the tarball exists alongside the registries. @@ -25,4 +27,4 @@ editor stability across a play-stop-play cycle. ## Required -- [Package](/quest/m1/cpp/package.md) - the tarball the module links +- [C++ through moq-ffi](/quest/m1/cpp/README.md) - the package the module links landed on this line (#4187) and reaches main with it diff --git a/quest/m3/video-hardware.md b/quest/m3/video-hardware.md index 6fd3453036..d2f420a456 100644 --- a/quest/m3/video-hardware.md +++ b/quest/m3/video-hardware.md @@ -1,4 +1,4 @@ -# [M] Video hardware validation +# [L] Video hardware validation ## Goal @@ -8,27 +8,76 @@ real hardware get run on it, and what breaks gets fixed. ## Plan Every item here is blocked on a physical machine rather than on code, which is -why they sit together and why they sit in m3. +why they sit together and why they sit in m3. Decided in the 2026-09-30 audit: +the KDE DMA-BUF capture and PipeWire camera validations fold in here, and the +V4L2 `VIDIOC_EXPBUF` source is dropped with the export itself (see +[#2819](/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md)). - **VAAPI low-power entrypoint and a second GPU.** H.264 encode, DMA-BUF input, and VPP resize ran on Intel Meteor Lake with iHD (moq-vaapi 0.1.0). Still unrun: the low-power encode entrypoint, which that device does not expose, and `MOQ_VAAPI_DEVICE` naming a node other than the first render node. -- **VAAPI input from V4L2 `VIDIOC_EXPBUF`.** DMA-BUF encode ran from a VA-API - decode and from PipeWire. A V4L2 export has not been the source. - **Windows Media Foundation capture**: on-demand open and close, so the camera LED is off when nobody is watching, and NV12 delivery from MJPEG and YUY2 cameras. - **A live camera run per platform**: capture needs device permission that a headless or agent process cannot grant itself. -Precedent for what this catches: NVENC validation on an RTX 3070 Ti found that -NVENC rejects stream-ordered pool memory, so buffers registered with it must -come from plain `cuMemAlloc`. That is not a bug any amount of review finds. +### PipeWire DMA-BUF capture on KDE + +Tracked in [#2893](https://github.com/moq-dev/moq/issues/2893). On a +KDE/Wayland desktop the portal source was selected, but the ignored +`portal_captures_frames` test never received a frame and timed out at 120 +seconds: + +```sh +cargo nextest run --profile ci -p moq-video --all-features --run-ignored ignored-only portal_captures_frames --no-capture +``` + +- Reproduce the post-selection timeout, then add tracing around portal + completion, PipeWire connection, format fixation, buffer allocation, and + first-frame delivery to locate the stall. +- A DMA-BUF-capable compositor produces `Surface::DmaBuf`; confirm the linear + DMA-BUF CPU fallback and shared-memory capture still work when DMA-BUF is + unavailable. +- Refine the ignored test so this failure is distinguishable from a + portal-selection timeout. + +### PipeWire cameras on a portal and a Pi + +Open `pipewire` and one `pipewire:` in a sandbox, where the portal +raises its permission dialog, and on a Raspberry Pi whose CSI camera is a +PipeWire node (spa-libcamera). Record the mode that opened, whether frames +arrived, and whether the producer used one memory block or one per plane. +No new capture API, and no libcamera source. + +A separate-plane producer belongs to +[multi-plane cameras](/quest/m2/pipewire-camera-planes.md); if that is why a +Pi produces nothing, write it down and stop. `doc/lib/rs/moq-video.md` says +both paths are reachable; correct that sentence if one cannot capture. + +### Fixes + +Fix only a defect a run hits. Precedent for what this catches: NVENC +validation on an RTX 3070 Ti found that NVENC rejects stream-ordered pool +memory, so buffers registered with it must come from plain `cuMemAlloc`. That +is not a bug any amount of review finds. ## Required - Someone with the hardware runs it: an Intel GPU exposing the VAAPI low-power - entrypoint, a second render node, a V4L2 capture device with DMA-BUF export, - a Windows machine with MJPEG and YUY2 cameras, and a live camera per platform + entrypoint, a second render node, a Windows machine with MJPEG and YUY2 + cameras, a live camera per platform, a KDE/Wayland desktop with an Intel or + AMD GPU, a sandbox that can show the camera portal dialog, and a Raspberry + Pi whose CSI camera appears as a PipeWire node + +## Closes + +- [#2893](https://github.com/moq-dev/moq/issues/2893) - close this issue when the quest finishes + +## Related + +- [#2819](/quest/m2/2819-moq-video-carry-pipewire-dma-bufs-safely-into-the-vulkan.md) - the DMA-BUF import validation this capture feeds +- [Capture multi-plane PipeWire cameras](/quest/m2/pipewire-camera-planes.md) - separate-plane I420 and NV12, when the Pi pass finds them +- [Embedded video path](/quest/m3/video-embedded.md) - presenting on a Pi, which is a different gap diff --git a/quest/m3/watch-data-sync.md b/quest/m3/watch-data-sync.md index a704971592..16a287344c 100644 --- a/quest/m3/watch-data-sync.md +++ b/quest/m3/watch-data-sync.md @@ -16,5 +16,10 @@ releases it. `delay` and `jitter` with `Sync` like a media rendition. - Snapshot tracks release the newest value at or before the playhead; stream tracks release every record in order. -- Take this up when an application needs synchronized data playback; until - then, a raw consumer reads payloads as they arrive. +- In m3 until an application needs synchronized data playback, such as + teleop telemetry beside video; until then, a raw consumer reads payloads as + they arrive. + +## Required + +- [Data track clock](/quest/m1/data-track-clock.md) - data timestamps share the media clock mapping, so the playhead can release them diff --git a/quest/m3/whep-abr.md b/quest/m3/whep-abr.md index af2367d95a..cc1442713e 100644 --- a/quest/m3/whep-abr.md +++ b/quest/m3/whep-abr.md @@ -8,6 +8,9 @@ over MoQ, so a weak link gets SD instead of stalling on HD. ## Plan +In m3 until a WHEP viewer needs adaptive quality; MoQ viewers already switch +renditions, and a WHEP viewer gets the best rendition today. + Open questions: whether to drive switching from str0m's bandwidth estimate (TWCC) or from loss and REMB, how to switch without a keyframe gap (subscribe the new rendition and splice at its next group, as the JS decoder does), and diff --git a/quest/m4/safari-webtransport.md b/quest/m4/safari-webtransport.md index df16c0f41e..85b3a454a0 100644 --- a/quest/m4/safari-webtransport.md +++ b/quest/m4/safari-webtransport.md @@ -13,7 +13,7 @@ this. - Name the exact Safari and iOS or iPadOS releases that ship the fix in the quest and in the `browser.ts` comment, then gate on them for Safari and for the iOS WebKit browsers, keeping older versions on the fallback. As of - 2026-09-08 the bug is still NEW with no fix released. + 2026-09-30 the bug is still NEW with no fix released. - Before flipping, rerun the raw WebTransport reproduction from #2388 (about 7,600 eleven-byte unidirectional streams, and about 16 MiB of data) on the fixed Safari, then a watch longer than two minutes in the QA harness on From cf3ec1188954b35bcfa1ea28da8b61dd36b707dc Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Wed, 30 Sep 2026 07:19:19 -0700 Subject: [PATCH 3/3] quest: one gate convention for every milestone A quest waiting on the outside world states its gate as a plain-text Required bullet in any milestone, re-checked by /quest-audit. Folded in from #4585, which is abandoned in favor of this. Co-Authored-By: Claude Opus 5.5 --- quest/README.md | 5 +++++ ...201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md | 4 ++++ quest/m3/README.md | 6 +++--- quest/m3/af-xdp.md | 4 ++++ quest/m3/livekit-shim.md | 4 ++++ quest/m3/quic-receive-ts.md | 4 ++++ quest/m3/whep-abr.md | 4 ++++ quest/m4/README.md | 5 ++--- 8 files changed, 30 insertions(+), 6 deletions(-) diff --git a/quest/README.md b/quest/README.md index e4b9dbdde2..38d4b4b9bc 100644 --- a/quest/README.md +++ b/quest/README.md @@ -16,6 +16,11 @@ offer. m4 waits on an upstream release. Priority is separate from branch targeting: published API and wire breaks still land on dev under the repository rules. +A quest waiting on the outside world, in any milestone, states that condition +as a plain-text `Required` bullet, so `quest ready` reports it blocked. +`/quest-audit` re-checks those gates; when one clears, remove the bullet and +move the quest to the milestone its priority belongs in. + ## Required - [m0: immediate priorities](/quest/m0/README.md) - everything in flight now: relay hardening and IETF interop for Seattle, wildcard routing, and audio playout diff --git a/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md b/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md index 53c502a3f5..9930a37408 100644 --- a/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md +++ b/quest/m3/3201-moq-uring-use-sendmsg-zc-for-large-udp-gso-trains.md @@ -39,6 +39,10 @@ Decided in the 2026-09-30 audit: moved to m3. The #3224 prototype was 6 to 9% slower, the win needs a physical-NIC sweep nobody has run, and io_uring ships in no package. +## Required + +- A physical-NIC remote peer to measure zero-copy sends against + ## Closes - [#3201](https://github.com/moq-dev/moq/issues/3201) - close this issue when the quest finishes diff --git a/quest/m3/README.md b/quest/m3/README.md index 4777061d72..57d3362074 100644 --- a/quest/m3/README.md +++ b/quest/m3/README.md @@ -8,9 +8,9 @@ Nothing here should start by opening an editor. ## Plan -A quest lands here when its gate is the outside world, not its priority. Each -states the condition in prose or as a plain-text `Required` bullet. When the -condition clears, move the quest to the milestone its work belongs in. A +A quest lands here when its gate is the outside world, not its priority. Its gate +is a plain-text `Required` bullet, re-checked as the root +[questline](/quest/README.md) describes. A speculative feature with no consumer parks here rather than in m2, and is deleted when it goes stale; git history keeps it. diff --git a/quest/m3/af-xdp.md b/quest/m3/af-xdp.md index 080d3d54dd..9ae34da3b0 100644 --- a/quest/m3/af-xdp.md +++ b/quest/m3/af-xdp.md @@ -22,3 +22,7 @@ closed until hardware changes. Decided in the 2026-09-30 audit: moved to m3. The relay packages don't ship io_uring yet, so a bypass that competes with it has no deployment to improve. + +## Required + +- [Relay io_uring packages](/quest/m2/relay-io-uring-package.md) - the io_uring path this is compared against ships first diff --git a/quest/m3/livekit-shim.md b/quest/m3/livekit-shim.md index bc3208d954..74e35d14d4 100644 --- a/quest/m3/livekit-shim.md +++ b/quest/m3/livekit-shim.md @@ -34,3 +34,7 @@ new rooms directly. - Mint tokens with `@moq/room`'s `claims(room, identity)`, which scopes the `publish` claim to the identity's subtree so participants cannot publish at each other's paths. + +## Required + +- A LiveKit user asks for a drop-in client diff --git a/quest/m3/quic-receive-ts.md b/quest/m3/quic-receive-ts.md index 30b07a4f2c..ad94b72070 100644 --- a/quest/m3/quic-receive-ts.md +++ b/quest/m3/quic-receive-ts.md @@ -30,6 +30,10 @@ Both ends must be ours, so this is native-only and serves native peers or relay-to-relay sessions. Decided in the 2026-09-30 audit: parked in m3 with GCC until such a consumer exists. +## Required + +- A native-to-native consumer of receive timestamps (browsers never see them) + ## Related - [QUIC GCC](/quest/m3/quic-gcc.md) - the controller that consumes it diff --git a/quest/m3/whep-abr.md b/quest/m3/whep-abr.md index cc1442713e..b91c396e61 100644 --- a/quest/m3/whep-abr.md +++ b/quest/m3/whep-abr.md @@ -17,3 +17,7 @@ the new rendition and splice at its next group, as the JS decoder does), and whether offering the renditions as WebRTC simulcast layers (RIDs) buys anything for a receive-only browser. Keep the best rendition (`hang::catalog::Video::ranked`) as the starting rendition. + +## Required + +- A WHEP viewer that needs rendition switching diff --git a/quest/m4/README.md b/quest/m4/README.md index 73be8d27bb..5ef8cbfb66 100644 --- a/quest/m4/README.md +++ b/quest/m4/README.md @@ -7,9 +7,8 @@ so it is not forgotten. ## Plan -Each quest states its gate as a plain-text `Required` bullet. Re-check the -gates periodically; when one clears, remove the bullet and promote the quest to -the milestone its priority belongs in. +Each quest states its gate as a plain-text `Required` bullet, re-checked as the +root [questline](/quest/README.md) describes. ## Required