Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ lite-07 stream type here.
- [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, and the media fixture's cold-start audio-tone flake is fixed at its cause
- [Container reads drop backfill](/quest/m1/read-backfill.md) - the JS container consumer drops groups below its read cursor like Rust
- [qmux no-wait opens](/quest/m1/qmux-no-wait.md) - the in-tree @moq/qmux rejects an over-limit create when waitUntilAvailable is false, like Chrome
- [qmux local lifecycle](/quest/m1/qmux-local-lifecycle.md) - an abort blocked on send credit resets at once, and a close behind a backed-up socket still sends APPLICATION_CLOSE
- [JS release preflight](/quest/m1/js-publish-preflight.md) - `release.ts` refuses a package whose `@moq/*` dependency is not on npm, so no dependent publishes uninstallable
- [Stream sockets disable Nagle](/quest/m1/tcp-nodelay.md) - every qmux TCP, TLS, and WebSocket socket, accepted or dialed, sets `TCP_NODELAY`
- [TS fidelity and recovery](/quest/m1/ts-reliability/README.md) - both modes have explicit profiles, automated validation, loss tests, and a dedicated guide
- [Resumed close](/quest/m1/resume-closed.md) - a resumed JS track keeps its declared end across copies, and its `closed` and `largest()` settle on a clean end
Expand All @@ -106,6 +108,7 @@ lite-07 stream type here.
- [WebGPU on Safari](/quest/m1/webgpu-safari.md) - the WebGPU renderer is verified on Safari 26 for macOS and iOS
- [Interop finish](/quest/m1/interop-finish.md) - the interop matrix proves a clean broadcast finish across implementations
- [Exporters follow a rendition to a new track](/quest/m1/export-rendition-rebind.md) - the fMP4, TS, MKV and FLV exporters rebuild a rendition whose resolved `track` or `broadcast` changes under the same ID
- [HLS cursor failure](/quest/m1/hls-segments-failure.md) - the export segment cursor returns a failed timeline as an error after draining, so a truncated recording never looks complete
- [Track live signal](/quest/m1/track-live.md) - a reader can wait for its track's feed to be live, so a player skips old cached groups while a fresh upstream subscription starts
- [moq-net compiles once](/quest/m1/net-erase-transport.md) - the session drivers stop monomorphizing per transport and per crate, saving several MiB of the relay, FFI library, `libmoq` and CLI
- [Audio devices on ARM](/quest/m1/audio-devices-arm.md) - `just rs audio-devices` passes on the hosted ARM runner
Expand Down
33 changes: 33 additions & 0 deletions quest/m1/hls-segments-failure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# [S] The HLS segment cursor reports a failed timeline

## Goal

`moq-hls`'s export `segments::Consumer::next` drains the segments a failed
timeline still covers, then returns the failure as an error. `Ok(None)`
means only a clean end, so a recorder that mirrors a broadcast to storage
can't take a truncated recording for a complete one.

## Plan

Decided 2026-10-22 (maintainer, from the release audit): fix it before the
cut, since changing what `Ok(None)` means is a breaking change.
[moq-dev/moq#6153](https://github.com/moq-dev/moq/pull/6153) fixed the
playlist side and named this follow-up.

In `rs/moq-hls/src/export/segments.rs`, `Consumer::next` returns `Ok(None)`
when `rendition.is_failed(&row)`, and its doc comment says so. A shared
reference failure ends the same way: `Producer::fail` records `failed`
without setting `ended`, the closed window maps to `Ready(None)`, and
`next_after` never reads `failed`. `Producer::failure()` holds the error
nobody reads.

Return the window's or rendition's failure once the covered segments drain.
Fix the doc comment and add a `rs/moq-hls/MIGRATE.md` entry. Test the
cursor alongside the playlist for both failure sources: valid segments
first, then the error, never `Ok(None)`.

The cursor has no consumer outside its tests yet. It stays public because
it is the documented entry point for mirroring to storage.
Comment thread
kixelated marked this conversation as resolved.
Resolved

Public API: `segments::Consumer::next` returns an error where it returned
`Ok(None)` (breaking). Wire: none.
35 changes: 35 additions & 0 deletions quest/m1/js-publish-preflight.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# [S] JS release refuses a package whose @moq dependency is missing

## Goal

`js/common/release.ts` refuses to publish a package to npm when any
`@moq/*` dependency range in its built `package.json` resolves to no
published version, and names the missing ones. A failed dependency publish
can no longer leave a dependent on npm that fails to install.

## Plan

Decided 2026-10-22 (maintainer, from the release audit): requested as a
follow-up by [moq-dev/moq#6243](https://github.com/moq-dev/moq/pull/6243).

`release-js.yml` runs `bun --filter '*' release`, which orders packages by
dependency but still runs dependents after a dependency's script fails.
`release.ts` skips a version already on npm and otherwise publishes; its only
`@moq/*` existence check gates the JSR dry run. `js/common/package.ts`
rewrites `workspace:^` to `^<version>` without checking npm.

Before `npm publish`, read the dependencies and peer dependencies of
`dist/package.json`, run `npm view <name>@<range> version` for each `@moq/*`
entry, and exit non-zero listing every range with no match. Reuse the
existing JSR dry-run lookup instead of adding a second one. Confirm that
`deno publish` already refuses an unresolvable `jsr:` import; extend the
check to JSR only if it does not.

Prove it on a dry run that depends on an unpublished `@moq/*` version: the
run fails, naming the package and range.

Public API: none. Wire: none.

## Related

- [First npm publish](/quest/m2/release/npm-first-publish.md) - the one-time hand publish of the new names this check would otherwise refuse
47 changes: 47 additions & 0 deletions quest/m1/qmux-local-lifecycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# [S] qmux aborts and closes without the peer's help

## Goal

In the in-tree `@moq/qmux` (`js/qmux`), `writer.abort()` on a stream whose
write is waiting for stream or connection send credit rejects that write and
sends RESET_STREAM at once. `close()` closes the socket only after
APPLICATION_CLOSE is handed to it, or after a bounded stall: a backed-up
WebSocket that drains in time delivers the frame, and one still wedged at the
deadline closes without it. Neither outcome waits for the peer to grant
credit, and close never relies on a fixed delay. Rust
`rs/qmux` already behaves this way and is out of scope.

## Plan

Decided 2026-10-22 (maintainer, from the release audit): one quest for both,
since both live in `js/qmux/src/session.ts` and share one contract: local
lifecycle never depends on peer cooperation. Both were deferred by
[moq-dev/moq#6124](https://github.com/moq-dev/moq/pull/6124).

Abort: the sink's `write` reaches `#claimSendCredit`, which awaits
`flow.sendCredit.claim()` and `#connCredit.claim(1n)`. `Credit.claim` in
`js/qmux/src/credit.ts` takes no signal, and WritableStream runs the sink's
`abort` only after the in-flight write settles, so the abort waits for credit.
Give `Credit.claim` an `AbortSignal` that removes its waiter and rejects, and
pass the sink controller's `signal` down through the stream data path, so the
existing abort handler sends RESET_STREAM immediately.

Close: `close()` and `#sendConnectionClose` queue APPLICATION_CLOSE, then
`sleep(100ms)` before `#transportClose()`. The scheduler holds the frame until
`sink.ready()`, and the WebSocket ponyfill holds a write while
`bufferedAmount` is high, so a backed-up socket closes with the frame never
sent. Replace both sleeps with a promise the scheduler resolves once the
control lane has flushed. Drop queued stream frames first, so
APPLICATION_CLOSE goes out next. Bound the wait like Rust's `CLOSE_TIMEOUT`
(`rs/qmux/src/session.rs`), started only while the writer is stalled.

Test with the controlled clock and a wedged socket: an abort blocked on each
credit kind emits RESET_STREAM with no peer grant; a close behind a socket
that drains before the deadline writes APPLICATION_CLOSE before the transport
closes; and one that never drains closes at the deadline without it.

Public API: none. Wire: none.

## Related

- [qmux no-wait opens](/quest/m1/qmux-no-wait.md) - another fix in the same file; its PR is [moq-dev/moq#6271](https://github.com/moq-dev/moq/pull/6271)
2 changes: 2 additions & 0 deletions quest/m2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ ranked just below deterministic TS legs, which it requires.
- [AudioToolbox decode](/quest/m2/audio-decode-audiotoolbox.md) - macOS and iOS decode HE-AAC, multichannel AAC, and what else the framework offers
- [AudioToolbox encode](/quest/m2/audio-encode-audiotoolbox.md) - macOS and iOS encode AAC-LC
- [CMAF surround Opus](/quest/m2/cmaf-opus-surround.md) - a released mp4-atom carries the `dOps` channel mapping table, and fMP4 import and export carry it through the OpusHead
- [moqsrc surround Opus](/quest/m2/moqsrc-opus-surround.md) - moqsrc sets the Opus mapping caps, so opusdec plays 3 to 8 channels
- [GPU CI](/quest/m2/gpu-ci.md) - NVIDIA tests gate the merge queue on a self-hosted GPU runner VM, and `just rs nvidia` runs them locally instead of skipping
- [VA-API renderer tests](/quest/m2/vaapi-render-tests.md) - the ignored VA-API renderer import tests run in `just rs gpu` on AMD and Intel and fail instead of skipping
- [Vendor-free CARLA bridge](/quest/m2/carla-bridge.md) - the CARLA demo's GPU bridge lets moq-video pick the encoder, and its stand-in test runs on AMD as well as NVIDIA
Expand All @@ -81,6 +82,7 @@ ranked just below deterministic TS legs, which it requires.
- [Front parking](/quest/m2/origin-front-parks.md) - an unroutable request waits on a front instead of re-asking on every route-table move
- [Route wakes](/quest/m2/route-wakes.md) - a route change wakes only the fronts it can move, so pool churn stops scaling with served paths
- [IETF lazy discovery](/quest/m2/ietf-lazy-discovery.md) - moq-transport sessions ask for announcements only under watched prefixes, starting with UNSUBSCRIBE_NAMESPACE on d14/15
- [Draft 14-16 session errors](/quest/m2/ietf-adapter-session-errors.md) - the moq-transport adapter keeps session-level error causes, so a terminal rejection is not redialed
- [Keyed prefix polling](/quest/m2/keyed-prefix-polling.md) - `SubscriberDriver` polls only the prefixes a wake concerns, if a bench shows the slope
- [Route health](/quest/m2/route-health/README.md) - epoch-only active/passive track recovery over metadata-only subscriptions
- [Wrapper traits over moq-net](/quest/m2/net-wrap/README.md) - moq-net exports producer and consumer traits, and every writer and reader above it takes them, so a wrapper such as e2ee passes through unchanged
Expand Down
11 changes: 11 additions & 0 deletions quest/m2/audit-quests.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ Run `/quest-audit` after the release cut. Read every outstanding quest with
parallel auditors, then verify source claims, active PR ownership, Required
ordering, and the latest maintainer decisions before changing plans.

Also reconcile merged follow-ups (decided 2026-10-22 from the release audit):
read the Follow-ups of every PR merged since the last release, and plan each
concrete remainder that has no quest, open PR, or recorded decline. The
release audit found seven such remainders across
[moq-dev/moq#6124](https://github.com/moq-dev/moq/pull/6124),
[moq-dev/moq#6153](https://github.com/moq-dev/moq/pull/6153),
[moq-dev/moq#6251](https://github.com/moq-dev/moq/pull/6251),
[moq-dev/moq#6243](https://github.com/moq-dev/moq/pull/6243),
[moq-dev/moq#4991](https://github.com/moq-dev/moq/pull/4991), and
[moq-dev/moq#5758](https://github.com/moq-dev/moq/pull/5758).

Baseline from the m1 run, 2026-10-22 UTC, against `main` at `bce217e063`:

- Read all 463 quest documents across m0-m3 and the root with parallel
Expand Down
35 changes: 35 additions & 0 deletions quest/m2/ietf-adapter-session-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# [S] The draft 14-16 adapter keeps session errors

## Goal

On moq-transport drafts 14 to 16, a session-level transport error from
`ControlStreamAdapter` reaches the session with its cause instead of as
`Closed`. A peer that ends the session with a terminal error, such as
ProtocolViolation or Unauthorized, is not redialed as if the drop were
transient.

## Plan

Decided 2026-10-22 (maintainer, from the release audit): m2. The race is
narrow and limited to the older drafts. It is the session-scope follow-up
left by [moq-dev/moq#6251](https://github.com/moq-dev/moq/pull/6251), which
kept stream errors through `AdapterError::Real`.

In `rs/moq-net/src/ietf/adapter.rs`, the adapter's `Session` impl maps the
native `poll_accept_bi`, `poll_open_uni`, `poll_accept_uni`, the datagram
send and receive, and `poll_closed` errors to `Error::Closed`. In
`ietf/session.rs`, `adapter_run` reads the real SETUP stream first and usually
keeps the peer's code. The code is lost when a unis, dispatch, or datagram
future fails first. The session then reports `Closed`, and
`moq-tokio`'s `is_terminal` treats that as retryable.

Carry the transport error through the way `AdapterError::Real` does, mapped
with the negotiated registry. Reproduce first: a draft-14 peer closes with
ProtocolViolation while a uni or datagram poll is pending, and the client
must see a terminal error and not redial.

Public API: none. Wire: none.

## Related

- [moq-net compiles once](/quest/m1/net-erase-transport.md) - its open PR [moq-dev/moq#6176](https://github.com/moq-dev/moq/pull/6176) touches the same adapter
32 changes: 32 additions & 0 deletions quest/m2/moqsrc-opus-surround.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# [S] moqsrc plays surround Opus

## Goal

`moqsrc` outputs `audio/x-opus` caps that `opusdec` accepts for a 3 to 8
channel Opus rendition, so surround Opus published through `moqsink` plays
back through GStreamer.

## Plan

Decided 2026-10-22 (maintainer, from the release audit): m2. Receive never
worked, so this is not a regression.
[moq-dev/moq#4991](https://github.com/moq-dev/moq/pull/4991) fixed publishing
and named this follow-up.

`rs/moq-gst/src/source/imp.rs` builds the caps from rate and channels, plus
`codec_data` and `stream-format=ogg` when a description exists. `opusdec`
ignores `codec_data` and reads `streamheader` or the mapping fields, so with
more than two channels it assumes family 0 and refuses.

Parse the catalog's OpusHead description with the existing `opus::Mapping`
and set `channel-mapping-family`, `stream-count`, `coupled-count`, and
`channel-mapping`. This needs no synthesized OpusTags, unlike a
`streamheader`. Test `opusenc` 5.1 through `moqsink`, a relay, `moqsrc`, and
`opusdec`, checking six decoded channels. The only moqsrc test rendition
today is stereo.

Public API: none. Wire: none.

## Related

- [CMAF surround Opus](/quest/m2/cmaf-opus-surround.md) - the same mapping table through fMP4 `dOps`, independent of this caps path
1 change: 1 addition & 0 deletions quest/m2/release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ production stability proven merely because upstream artifacts published.
- [API-diff check](/quest/m2/release/migrate-audit.md) - PRs into release diff the public API; backports refuse breaks, the cut confirms MIGRATE.md coverage
- [API shape review](/quest/m2/release/api-review.md) - every new or changed public API reviewed for a better shape before the cut
- [Feature review](/quest/m2/release/feature-review.md) - every major work line since the last release judged end to end before the cut
- [Claim new npm names](/quest/m2/release/npm-first-publish.md) - a maintainer claims `@moq/clock`, `libav`, `video`, and `p2p` on npm and JSR with trusted publishing, before the cut publishes them

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Claim package names before enabling the m1 preflight

This prerequisite is scheduled under release execution, which starts only after m1, but the m1 preflight is intended to fail dry runs while an @moq/* range is unpublished. Because the nightly workflow already invokes release-js.yml on main and these four names remain absent until this quest runs, landing the preflight first would make that nightly fail throughout the rest of m1. Move the name-claiming action ahead of the preflight, or make it an explicit m1 prerequisite, so the required nightly remains viable.

AGENTS.md reference: AGENTS.md:L28-L28

Useful? React with 👍 / 👎.

- [Cut and publish](/quest/m2/release/cut.md) - independently executable after m1; release-triggered publication
- [Published assets](/quest/m2/release/assets-release.md) - npm carries hostable worklets
- [C++ publication](/quest/m2/release/cpp-release.md) - archives and matching OBS plugin publish and install
Expand Down
1 change: 1 addition & 0 deletions quest/m2/release/cut.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ the new `/quest/m1/README.md`, and the cut also requires both reviews. Only
- [API-diff check](/quest/m2/release/migrate-audit.md) - every break on the cut PR has its MIGRATE.md entry
- [API shape review](/quest/m2/release/api-review.md) - every new or changed public API was reviewed for a better shape, and approved changes landed
- [Feature review](/quest/m2/release/feature-review.md) - every major work line since the last release was judged end to end, and its follow-ups landed
- [Claim new npm names](/quest/m2/release/npm-first-publish.md) - trusted publishing can publish every new `@moq/*` package
42 changes: 42 additions & 0 deletions quest/m2/release/npm-first-publish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# [XS] Claim the new @moq npm and JSR names

## Goal

`@moq/clock`, `@moq/libav`, `@moq/video`, and `@moq/p2p` exist on npm, and
the three with JSR packages exist on JSR, each with trusted publishing
attached to `moq-dev/moq`. The cut's release workflow can then publish
their real versions. A maintainer does this by hand. Delete this quest once
the checks pass:

- `npm view @moq/<name>` resolves for all four, and the package settings show
the `moq-dev/moq` trusted publisher.
- `https://api.jsr.io/scopes/moq/packages/<name>` returns 200 for `clock`,
`video`, and `p2p`, each linked to the repository. `@moq/libav` sets
`"jsr": false`.

## Plan

Found 2026-10-22 while planning the release audit's follow-ups: all four
names return 404, and nearly every published package (net, hang, watch,
publish, auth, signals, and more) now depends on `@moq/clock`. npm trusted
publishing attaches only to an existing package, so without this the cut's
publish fails on these names and still publishes dependents that can't
install. [moq-dev/moq#6243](https://github.com/moq-dev/moq/pull/6243) raised
it for `@moq/libav` alone.

Decided 2026-10-22 (maintainer): publish a minimal `0.0.0` placeholder
(README only) per name to claim it, then attach the trusted publisher. The
cut publishes the real `0.1.0` through the workflow with provenance, so no
real version is published by hand. `release.ts` compares against the
`latest` tag, so the placeholder does not suppress that publish.

Until the cut publishes `@moq/libav` 0.1.0, Windows `bun install` keeps
failing, since `js/libav/build.ts` there unpacks the published tarball of
the workspace version. The placeholder does not change that.

Public API: none. Wire: none.

## Related

- [Release preflight](/quest/m1/js-publish-preflight.md) - refuses a dependent whose `@moq/*` dependency did not publish
- [Dart publication](/quest/m2/release/dart-publish.md) - the same trusted-publishing check for pub.dev
1 change: 1 addition & 0 deletions quest/m3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ m4's upstream waits in here and moved m2 work with no consumer.
- [fMP4 emsg](/quest/m3/emsg.md) - settles the shared framing and missing-data semantics before this section adopts them
- [ID3 catalog section](/quest/m3/id3.md) - timed ID3 as a first-class container-neutral catalog section
- [FLV script tags](/quest/m3/flv-script.md) - onMetaData and AMF data messages survive RTMP and FLV import
- [Half and quarter rate AC-3](/quest/m3/ac3-low-rate.md) - bsid 9 and 10 AC-3 parse through TS and FLV instead of being refused
- [MSF feedback tracks](/quest/m3/msf-publish-tracks.md) - `--stats` and `--echo` work under `--catalog-format msf` through `publishTracks`, once a consumer asks
- [MSFTS convergence](/quest/m3/msfts-convergence.md) - the demultiplexed TS lane converges on MSFTS where they differ, once an MSFTS implementer engages
- [#2279](/quest/m3/2279-hang-typed-scte-35-ad-cue-signaling-carried-opaquely.md) - hang: SCTE-35 cues arrive immediately on an independent metadata track, optionally associated with a rendition
Expand Down
21 changes: 21 additions & 0 deletions quest/m3/ac3-low-rate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# [XS] Half and quarter rate AC-3

## Goal

`moq-mux` parses AC-3 with bsid 9 and 10, the half and quarter sample-rate
variants, through TS and FLV import and TS export, instead of refusing them.

## Plan

Decided 2026-10-22 (maintainer, from the release audit): support them rather
than only documenting the refusal. It is cheap and removes an inconsistency
noted in [moq-dev/moq#5758](https://github.com/moq-dev/moq/pull/5758):
`ac3_core` in `rs/moq-mux/src/ts/export.rs` counts bsid up to 10 as an AC-3
core, then hands it to `ac3::parse_header`, which refuses anything above 8.

In `rs/moq-mux/src/codec/ac3.rs`, keep the frame-size tables and shift the
sample rate right by `bsid - 8`. Add a bsid 9 or 10 fixture through TS import
and TS export. Broadcast specs require 48 kHz, so these streams are rare, and
this stays low priority.

Public API: none. Wire: none.
Loading