From 1aab0d89488dc51e267c70bb00f0dd5e2510fffc Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 20 Sep 2026 06:32:08 -0700 Subject: [PATCH 1/5] quest: record archive, e2ee, and uring audit follow-ups Co-Authored-By: Codex --- quest/m2/README.md | 1 + quest/m2/archive/proof.md | 9 +++++++++ quest/m2/e2ee/README.md | 1 + quest/m2/e2ee/receiver-failure.md | 32 ++++++++++++++++++++++++++++++ quest/m2/e2ee/rust.md | 3 ++- quest/m2/uring-handshake-cancel.md | 31 +++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 quest/m2/e2ee/receiver-failure.md create mode 100644 quest/m2/uring-handshake-cancel.md diff --git a/quest/m2/README.md b/quest/m2/README.md index 92eb0b3dfc..4ad8caabe4 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -66,6 +66,7 @@ before format-specific metadata. Unrelated areas can proceed in parallel. - [Publisher clocks](/quest/m2/publisher-clock.md) - wire the shared clock through native and browser publisher restarts - [Broadcast route](/quest/m2/js-broadcast-route.md) - JS Announce.Broadcast goes live on any claim matching its path, like Rust routed() - [io_uring check](/quest/m2/check-uring-feature.md) - a moq-relay diff compiles the io-uring feature in `just check`, not only nightly +- [io_uring handshake cancellation](/quest/m2/uring-handshake-cancel.md) - dropping a pending handshake releases its connection while the worker keeps running - [Flaky timing tests](/quest/m2/flaky-timing-tests.md) - three real-clock tests become deterministic instead of failing under load - [Binding stats docs](/quest/m2/binding-stats-docs.md) - every binding's doc page lists its connection stats fields with units diff --git a/quest/m2/archive/proof.md b/quest/m2/archive/proof.md index 1d6d6a6bb3..b3a9b1478f 100644 --- a/quest/m2/archive/proof.md +++ b/quest/m2/archive/proof.md @@ -42,6 +42,15 @@ and stale media listings preceding a new timeline commit. Following N+1 must not refresh all media listings. Wire these cases into CI for the store, writer, and reader implementations; do not add an unconnected standalone proof script. +The store's own tests also cover recording-prefix isolation (`rec` beside +`rec-other`), empty prefixes, track-prefix listings, continuation pages, and +every supported pagination option. A page must not lose directory entries +silently or fail because the backend matched a neighbouring recording. Every +publicly constructible key either serializes to a path its parser accepts or +fails before storage; direct `Key::Groups` construction must not bypass range +validation. These cases belong beside the store and codec code and need no +new public API or format change. + Crash a DVR writer after its pop becomes durable but before media deletion. On exclusive restart, prove that expired and uncommitted group objects are removed after the grace period while retained media, `.info`, and checkpoint diff --git a/quest/m2/e2ee/README.md b/quest/m2/e2ee/README.md index 47f169ad55..e2081c989c 100644 --- a/quest/m2/e2ee/README.md +++ b/quest/m2/e2ee/README.md @@ -44,6 +44,7 @@ The Rust and TypeScript cores expose the same surface, and nothing else: ## Quests +- [Receive failure](/quest/m2/e2ee/receiver-failure.md) - failed opens consume the key budget and a bad grouped frame wakes and terminates every pending read - [Opaque broadcast path](/quest/m2/e2ee/path.md) - the draft, vectors, and questline publish at `/` with no `.e2ee` suffix - [Rust E2EE core on moq-e2ee-00](/quest/m2/e2ee/rust.md) - reshape the merged diff --git a/quest/m2/e2ee/receiver-failure.md b/quest/m2/e2ee/receiver-failure.md new file mode 100644 index 0000000000..eb0ffc5947 --- /dev/null +++ b/quest/m2/e2ee/receiver-failure.md @@ -0,0 +1,32 @@ +# [M] Enforce E2EE receive limits and terminal failure + +## Goal + +Every attempted AEAD open consumes its invocation budget, and a grouped +authentication failure terminates the subscriber and wakes pending reads. +Bad datagrams remain typed events until a key limit is exhausted. Preserve +the public API and the ciphertext format. + +## Plan + +The release API audit found `TrackKey::open` increments usage only after +decryption succeeds (`rs/moq-e2ee/src/key.rs`), so repeated invalid tags do +not approach the invocation limit. `group::Consumer::fail_auth` only stores +an atomic flag; reads already parked in the underlying subscriber do not +receive a wake and the failed subscriber keeps its demand alive. + +Reproduce both behaviors in the crate's CI tests. Charge actual AEAD attempts, +including failures, while keeping malformed-input prechecks and plaintext-byte +accounting consistent with the profile. A failed datagram must not mark its +sequence as successfully received and suppress a later authentic one. + +Propagate grouped failure through shared terminal state and release the +underlying demand. Pending group, frame, and datagram reads must observe the +same terminal authentication error without another publisher event. Test +sibling handles and a publisher that remains open and silent after the bad +frame. Use test-private near-limit counters instead of millions of operations; +do not add exported test hooks. + +## Related + +- [Rust E2EE core](/quest/m2/e2ee/rust.md) - API and profile alignment must preserve these receive invariants diff --git a/quest/m2/e2ee/rust.md b/quest/m2/e2ee/rust.md index d8a57aa614..d7a970402d 100644 --- a/quest/m2/e2ee/rust.md +++ b/quest/m2/e2ee/rust.md @@ -14,7 +14,7 @@ The crate is unpublished, so this is a reshape in place, not a compatibility lay - Add `Credential::path(semantic) -> Path`, the epoch-free opaque broadcast name from [Opaque broadcast path](/quest/m2/e2ee/path.md), and `Generation` from `credential.generation(epoch)`. It owns `name(semantic) -> Name`, `produce(moq_net::track::Producer) -> track::Producer`, `consume(moq_net::track::Subscriber) -> track::Consumer`, and `Epoch::mint()` returning a lowercase UUIDv7 via the `uuid` crate (`v7` feature, added to `[workspace.dependencies]`). `Name` replaces `PhysicalName`; `Epoch` is a validated path segment (nonempty, no `/`, at most 65535 bytes). - Delete `Publication` and its process-global generation set, `retransmit_datagram`, `datagram_ciphertext`, `group::Producer::ciphertext`, the producer-side datagram retention map, `GroupWindow`, `set_subscribe`, `datagram_payload_limit`, `varint_len`, and the `catalog` module. Keep `TrackKey`, `protect`, `open`, and `nonce` crate-private. - `track::Producer` allocates sequences monotonically and refuses `create_group` or `insert_datagram` below the next sequence with `Reuse`; that is the only reuse rule. Frames are numbered by write order. The datagram plaintext cap is the constant `MAX_DATAGRAM_PLAINTEXT` (1160). -- `track::Consumer` keeps the datagram sliding window as a 1024-bit bitmask below the greatest opened sequence, marks only after a successful open, counts failed opens against the key, and aborts the inner subscriber once a grouped frame fails authentication so a caller cannot keep polling a dead track. +- `track::Consumer` keeps the datagram sliding window as a 1024-bit bitmask below the greatest opened sequence and marks only after a successful open. Preserve the receive limits and terminal-state behavior covered by [Receive failure](/quest/m2/e2ee/receiver-failure.md); that implementation-only fix is independently landable. - Swap `include_str!("../../../drafts/moq-e2ee-01.json")` for the `-00` vectors, then delete `drafts/moq-e2ee-01.json` and `drafts/moq-e2ee-01.ts`; `just drafts check` globs the remaining generator. Keep the lifecycle tests the draft requires: monotonic allocation, exhaustion with failed opens counted, bounded datagram suppression, and a new epoch authenticating while the old keys do not. - Update `doc/lib/rs/index.md`, the crate README, and the changelog to the new surface. No wire change; the draft already carries the profile. @@ -24,4 +24,5 @@ The crate is unpublished, so this is a reshape in place, not a compatibility lay ## Related +- [Receive failure](/quest/m2/e2ee/receiver-failure.md) - key usage accounting and waking terminal reads, without an API change - [TypeScript E2EE core](/quest/m2/e2ee/typescript.md) - mirrors this surface name for name diff --git a/quest/m2/uring-handshake-cancel.md b/quest/m2/uring-handshake-cancel.md new file mode 100644 index 0000000000..ec2983b0b8 --- /dev/null +++ b/quest/m2/uring-handshake-cancel.md @@ -0,0 +1,31 @@ +# [M] Cancelling an io_uring handshake releases its connection + +## Goal + +Dropping a pending QUIC dial or WebTransport handshake closes the abandoned +connection and releases its driver and endpoint bookkeeping while the worker +continues running. No new public API or wire format is needed. + +## Plan + +The release API audit found that `quic::web::Request::accept` closes its +connection when `handshake` returns an error, but creates its drop guard only +after the handshake completes. Cancellation at an earlier await bypasses that +error arm. Connection drivers retain their own state, so dropping the future's +connection handles does not itself close the connection. The peer can keep it +active beyond the idle timeout. Inspect the equivalent ownership transfer in +`Endpoint::connect` and `connection::establish` too. + +Reproduce before changing the code. Establish cleanup ownership before the +first suspension point and transfer it only when the public result is handed +off. Preserve successful sessions and unrelated connections sharing the +endpoint; do not add a timeout or retry to hide abandoned ownership. + +Linux CI covers cancellation during QUIC establishment, before peer SETTINGS, +and while awaiting CONNECT. Keep the worker driven, assert the abandoned +connection reaches a terminal state and its bookkeeping drains, and prove a +sibling connection still works. Include the successful handoff case. + +## Related + +- [Application close delivery](/quest/m2/quic/uring-close.md) - submission and worker teardown after a close is requested From a8b957099dbf876718b6c56e1f0baaf6148116bd Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 20 Sep 2026 06:40:32 -0700 Subject: [PATCH 2/5] quest: gate release on the 0.0.x package APIs Co-Authored-By: Codex --- quest/README.md | 33 +++---- quest/m0/README.md | 97 ++++++++++++------- quest/m0/archive-listing.md | 40 ++++++++ quest/m0/archive-ranges.md | 34 +++++++ quest/{m2/e2ee/rust.md => m0/e2ee-api.md} | 22 +++-- quest/{m2/e2ee/path.md => m0/e2ee-path.md} | 0 .../reuseport-group.md => m0/sock-group.md} | 17 +++- quest/m0/uring-identity.md | 44 +++++++++ quest/m1/README.md | 5 +- quest/m1/release.md | 7 +- ...s-dropping-one-split-server-resizes-the.md | 4 +- quest/m2/README.md | 2 - quest/m2/api-unreleased-crate-names.md | 32 ------ quest/m2/e2ee/README.md | 13 +-- quest/m2/e2ee/receiver-failure.md | 2 +- quest/m2/e2ee/typescript.md | 2 +- quest/m2/one-port/README.md | 2 +- 17 files changed, 243 insertions(+), 113 deletions(-) create mode 100644 quest/m0/archive-listing.md create mode 100644 quest/m0/archive-ranges.md rename quest/{m2/e2ee/rust.md => m0/e2ee-api.md} (58%) rename quest/{m2/e2ee/path.md => m0/e2ee-path.md} (100%) rename quest/{m2/reuseport-group.md => m0/sock-group.md} (62%) create mode 100644 quest/m0/uring-identity.md delete mode 100644 quest/m2/api-unreleased-crate-names.md diff --git a/quest/README.md b/quest/README.md index 1f756f1563..b8471d561e 100644 --- a/quest/README.md +++ b/quest/README.md @@ -7,28 +7,27 @@ grouped into milestones ordered by priority. ## Plan -m0 contains the immediate media work: the Pronto desktop GPU path and the four -0.0.x media crate API gates before 0.1. m1 is the dev branch line. m2 is the -next agent -wave across reliability, features, performance, and planning. Unsettled -quests may stay in m2 for planning; their implementation waits for the -required decisions. m3 holds later features, design studies, experiments, -and hardware validation. m4 is deferred: work whose first step is outside -this repository. Priority is separate from branch targeting: -published API breaks still target dev under the repository rules. An issue -already fixed on dev stays open until dev merges. +m0 contains the immediate priorities: the release API gates for the archive, +E2EE, socket, uring, and media packages, plus the Pronto desktop GPU path. m1 +is the dev branch line. m2 is the next agent wave across reliability, features, +performance, and planning. Unsettled quests may stay in m2 for planning; their +implementation waits for the required decisions. m3 holds later features, +design studies, experiments, and hardware validation. m4 is deferred: work +whose first step is outside this repository. Priority is separate from branch +targeting: published API breaks still target dev under the repository rules. An +issue already fixed on dev stays open until dev merges. The 2026-09 audit keeps uring-TCP in m2 and defers catalog identity, mobile -ownership and dependent capture, Linux OBS GPU feasibility, the LiveKit -shim, and experimental QUIC probing. Independently useful binding, codec, -room, and transport work stays in m2. The 2026-09-19 transport grooming -settled one QUIC stack (a moq-dev fork of noq), moved capacity probing back -into m2, and opened m4 for the hardware- and partner-gated quests. +ownership and dependent capture, Linux OBS GPU feasibility, the LiveKit shim, +and experimental QUIC probing. Independently useful binding, codec, room, and +transport work stays in m2. The 2026-09-19 transport grooming settled one QUIC +stack (a moq-dev fork of noq), moved capacity probing back into m2, and opened +m4 for the hardware- and partner-gated quests. ## Quests -- [m0: immediate media](/quest/m0/README.md) - Pronto GPU delivery and the - audio, video, transcode, and NVENC contracts required before 0.1 +- [m0: immediate priorities](/quest/m0/README.md) - release API gates and the + reusable Pronto GPU path - [m1: the dev line](/quest/m1/README.md) - the breaking API and wire changes, the merge gates, and the merge itself - [m2: next wave](/quest/m2/README.md) - implementation and planning across diff --git a/quest/m0/README.md b/quest/m0/README.md index a1738d9106..c1b44eda82 100644 --- a/quest/m0/README.md +++ b/quest/m0/README.md @@ -1,25 +1,50 @@ -# m0: immediate media +# m0: immediate priorities ## Goal -Settle the public contracts of `moq-audio`, `moq-video`, `moq-transcode`, and -`moq-nvenc` before their first 0.1 releases, while supplying the reusable GPU -media support needed to remove raw-pixel CPU transfers from the Pronto CARLA -demo on its Linux/NVIDIA desktop. Product integration and installation live in -moq.pro. +Settle the public contracts of `moq-archive`, `moq-e2ee`, `moq-sock`, +`moq-uring`, `moq-audio`, `moq-video`, `moq-transcode`, and `moq-nvenc` +before the imminent release, while supplying the reusable GPU media support +needed to remove raw-pixel CPU transfers from the Pronto CARLA demo. These are +independent immediate tracks rather than mutual prerequisites. ## Plan -The Pronto GPU quests remain an independent deliverable within m0. They define -portable Vulkan/CUDA ownership, safe partial NVENC initialization, and a strict -GPU conversion path without changing the scope of the pre-0.1 API audit. - -The four audited crates are 0.0.x, so their changes target main. Adapt callers -in other packages without breaking their published APIs, C layouts, or wire -formats. Do not bump versions as part of these quests. The final review records -when the four crates are ready for a separately requested release. - -The package boundaries are explicit: +The archive, E2EE, socket, and uring crates are 0.0.1 on main after the dev +merge. Their six API quests gate the release and target main under the 0.0.x +exception. Inspect transitive public exposure before changing a shared symbol: +`moq-tokio` publicly re-exports `moq-sock`'s bind module. Keep that re-export +and its current names. + +Keep the useful boundaries: archive owns storage and codecs, E2EE owns +protection rather than catalogs, sock owns runtime-neutral sockets, and uring +owns the local worker and its I/O. Prefer standard Rust ranges to a new public +range type. Keep uring's root `Config` reachable, since worker is private; +renaming `TxBuf` or moving bind names does not improve an ownership contract. + +Their package boundaries are explicit: + +- `moq-archive` replaces reversed integer-pair bounds with validated finite + `RangeInclusive` values, unifies streaming and paginated listing under + archive-owned query/entry types, and hides path helpers that are not consumer + APIs. Persisted object paths and bytes remain unchanged. +- `moq-e2ee` replaces raw/profile-global construction with application-owned + secrets and epoch-scoped `Credential`, `Generation`, `Epoch`, and track/group + handles. Raw crypto, catalog policy, retransmission internals, and the global + `Publication` registry leave the public surface. +- `moq-sock` makes incomplete reuseport groups unservable and retains every + member socket for the served group's lifetime. +- `moq-uring` derives worker and steering identity from owned sockets and + connections instead of independently supplied handles or shard values. +- Published `moq-tokio` keeps its worker signatures and `bind` re-export while + adapting internal plumbing. Its root names do not move. + +The media crates are also 0.0.x, so their changes target main. Adapt callers in +other packages without breaking their published APIs, C layouts, or wire +formats. Do not bump versions as part of these quests. The media review records +when the four crates are ready for a separately requested 0.1 release. + +Their package boundaries are explicit: - `moq-audio` owns the PCM/layout and codec configuration split, decoder entry point, publication authority, FEC removal, AEC attachment, playback outcome, @@ -35,36 +60,40 @@ The package boundaries are explicit: `moq-ffi`, `libmoq`, and language-binding signatures, layouts, and sentinel behavior remain unchanged while their internals adapt. -The agreed direction is small, honest APIs: typed PCM layouts, rational video -rates, extensible GOP and frame records, explicit ownership, and no knobs that -claim behavior they do not provide. Synchronous codecs remain public and +The agreed media direction is small, honest APIs: typed PCM layouts, rational +video rates, extensible GOP and frame records, explicit ownership, and no knobs +that claim behavior they do not provide. Synchronous codecs remain public and thread-confined; async sinks own codec execution. Native versus CPU output is a -choice, not a promise that every native backend yields a GPU surface. +choice, not a promise that every native backend yields a GPU surface. OpenH264 +becomes optional but stays enabled by default. Rendering becomes opt-in; +inexpensive native codec defaults remain. -OpenH264 becomes optional but stays enabled by default. Rendering becomes -opt-in; inexpensive native codec defaults remain. Hardware libraries already -loaded at runtime do not need a toolkit dependency added to the build. - -Each quest updates the existing crate documentation, examples, and affected -callers with its change. Contract tests run in CI; feature checks exercise each -crate independently, since workspace feature unification hides missing gates. -Cross-platform compilation and hardware execution are separate evidence. +The Pronto GPU quests remain an independent deliverable within m0. They define +portable Vulkan/CUDA ownership, safe partial NVENC initialization, and a strict +GPU conversion path without changing either API audit. Product integration and +installation live in moq.pro. -The audit was source inspection at `b2ef453e67`, not fresh compilation or -benchmarking. All public module families and their in-tree consumers were -reviewed; the generated NVIDIA ABI was inspected only where wrapper safety -depended on it. Performance concerns remain hypotheses until measured. +Each implementation updates its existing README, examples, and affected docs +inline and adds regression coverage to normal or nightly CI. Feature checks +exercise each media crate independently, since workspace feature unification +hides missing gates. Cross-platform compilation and hardware execution are +separate evidence. The audits were source-based, not a cryptographic review, +fresh compilation, benchmark, or Linux runtime validation. API-preserving implementation, codec additions, allocation work, and hardware proof remain in the existing backlog. Keep audio's integrated packetizing Producer and transcode's validated Ladder and coalescing active cursor. Keep one video Frame/Surface hierarchy and its deliberate native/wgpu type interop; do not add another media abstraction or a renderer crate during stabilization. -Preserve the documented ability to abort publication after finish unless an -actual ownership fix requires a replacement error-finalization contract. ## Quests +- [E2EE path](/quest/m0/e2ee-path.md) - align the unpublished path derivation and vectors before the core adopts them +- [E2EE API](/quest/m0/e2ee-api.md) - epoch-scoped ownership replaces raw crypto, catalog helpers, and process-global claims +- [Socket group](/quest/m0/sock-group.md) - complete formation and retained sockets precede usable serving handles +- [uring identity](/quest/m0/uring-identity.md) - sockets and connections carry their worker and steering identity +- [Archive ranges](/quest/m0/archive-ranges.md) - one finite inclusive range convention replaces reversed integer pairs +- [Archive listing](/quest/m0/archive-listing.md) - one recording-scoped query exposes only supported listing behavior - [Vulkan/CUDA surfaces](/quest/m0/video-vulkan-cuda.md) - retain producer slots and synchronize GPU access safely across Vulkan and CUDA - [NVENC registration rollback](/quest/m0/nvenc-registration.md) - release resources when mapping fails after registration diff --git a/quest/m0/archive-listing.md b/quest/m0/archive-listing.md new file mode 100644 index 0000000000..a450e6ba47 --- /dev/null +++ b/quest/m0/archive-listing.md @@ -0,0 +1,40 @@ +# [M] Archive listing has one recording-scoped query + +## Goal + +Streaming and paginated archive listings use the same prefix and offset +convention and expose only behavior their result type can represent. Callers +cannot accidentally prepend a recording prefix twice or request directory +results that are silently discarded. + +## Plan + +Today `List::prefix` takes a fully prefixed Path, while `list_paginated` +takes a relative string. The latter exposes object_store's entire +PaginatedListOptions but drops common_prefixes from its result. Its bare +recording prefix can also match a sibling such as `rec-other` when listing +`rec`, because paginated backend prefixes are lexical rather than segmented. + +Use an archive-owned `store::list::Query` and `Entry`, with one +recording-relative prefix/offset convention across both entry points. Keep +continuation tokens opaque and scope them to the query. Expose pagination +controls the archive supports, not arbitrary backend options. Directory +listing is outside this flat object API. Keep the generic Store and its +object_store escape hatch; do not add another storage abstraction. + +Fold duplicate free path-prefix helpers into the Store/query API and make +implementation-only helpers, including check_id, crate-private. Keep module +docs and examples consistent and report the exact removed exports. + +Regression tests cover a nonempty recording prefix, sibling recordings, +track prefixes, exclusive offsets, multiple pages, and every exposed option. +Passing the same logical query to either method must enumerate the same +objects when collected; backend order remains unspecified. A page must +neither silently drop directory results nor fail on neighbouring recordings. + +Public API: breaking list query and pagination options in moq-archive 0.0.1. +Wire and persisted format: unchanged. + +## Related + +- [Archive proof](/quest/m2/archive/proof.md) - backend and replay conformance diff --git a/quest/m0/archive-ranges.md b/quest/m0/archive-ranges.md new file mode 100644 index 0000000000..ccfa9346a4 --- /dev/null +++ b/quest/m0/archive-ranges.md @@ -0,0 +1,34 @@ +# [S] Archive group bounds use one inclusive range + +## Goal + +Archive callers pass group bounds without remembering an argument-order +exception. `Object::bounds`, range-named keys, reads, and validation use one +finite inclusive convention, with the persisted layout unchanged. + +## Plan + +`Object::bounds` returns `(smallest, largest)`, while `Key::groups`, +`Store::get_groups`, and `Object::{decode_groups,check_bounds}` take the +reverse order. Reuse standard Rust range notation: moq-net already accepts +`RangeBounds` in `Subscription::with_groups` and reader `set_groups`. +Archive objects require two finite bounds, so use `RangeInclusive` and +reject empty, reversed, or out-of-profile ranges at the boundary. Do not +invent another public Bounds type or expose moq-net's private normalization +helper; that helper supports unbounded subscription ranges with exclusive caps. + +Keep largest-first filename serialization private to the codec. Public +enum construction must not bypass validation when a key is serialized. +An object's returned bounds should pass directly to lookup and validation. + +Cover singleton and sparse ranges, reversed bounds, both identifier limits, +and direct key construction in the crate's CI tests. Preserve the exact +existing encoded paths and bytes. Update the examples and module docs inline; +this quest adds no recording or replay orchestration. + +Public API: breaking range arguments and return values in moq-archive 0.0.1. +Wire and persisted format: unchanged. + +## Related + +- [Archive proof](/quest/m2/archive/proof.md) - storage and replay conformance beyond the API change diff --git a/quest/m2/e2ee/rust.md b/quest/m0/e2ee-api.md similarity index 58% rename from quest/m2/e2ee/rust.md rename to quest/m0/e2ee-api.md index d7a970402d..fcc7e175d0 100644 --- a/quest/m2/e2ee/rust.md +++ b/quest/m0/e2ee-api.md @@ -6,21 +6,31 @@ [questline](/quest/m2/e2ee/README.md): epoch-scoped generations, monotonic identities, and no state that has to survive a publisher instance. -The crate is unpublished, so this is a reshape in place, not a compatibility layer. +Reshape the 0.0.x API in place without compatibility aliases. Check publication +status separately before replacing a wire profile. ## Plan -- Replace `Credential::new(profile, context, generation, kid, secret)` with `Credential::new(Config { context, kid, secret })` and `Credential::generate(context, kid)`. Drop `profile`, `Pin`, `check_pin`, `prk_bytes`, `name_info`, `key_info`, and `key_bytes` from the public surface; the crate is the profile and the vectors run in-crate. -- Add `Credential::path(semantic) -> Path`, the epoch-free opaque broadcast name from [Opaque broadcast path](/quest/m2/e2ee/path.md), and `Generation` from `credential.generation(epoch)`. It owns `name(semantic) -> Name`, `produce(moq_net::track::Producer) -> track::Producer`, `consume(moq_net::track::Subscriber) -> track::Consumer`, and `Epoch::mint()` returning a lowercase UUIDv7 via the `uuid` crate (`v7` feature, added to `[workspace.dependencies]`). `Name` replaces `PhysicalName`; `Epoch` is a validated path segment (nonempty, no `/`, at most 65535 bytes). +- Replace `Credential::new(profile, context, generation, kid, secret)` with `Credential::new(Config { context, kid, secret })`, accepting an application-owned 32-byte secret. Remove `Credential::generate`: it hides the generated secret and cannot provision another process or device. Drop `profile`, `Pin`, `check_pin`, `prk_bytes`, `name_info`, `key_info`, and `key_bytes` from the public surface; the crate is the profile and the vectors run in-crate. +- Add `Credential::path(semantic) -> Path`, the epoch-free opaque broadcast name from [Opaque broadcast path](/quest/m0/e2ee-path.md), and `Generation` from `credential.generation(epoch)`. It owns `name(semantic) -> Name`, `produce(moq_net::track::Producer) -> track::Producer`, `consume(moq_net::track::Subscriber) -> track::Consumer`, and `Epoch::mint()` returning a lowercase UUIDv7 via the `uuid` crate (`v7` feature, added to `[workspace.dependencies]`). `Name` replaces `PhysicalName`; `Epoch` is a validated path segment (nonempty, no `/`, at most 65535 bytes). - Delete `Publication` and its process-global generation set, `retransmit_datagram`, `datagram_ciphertext`, `group::Producer::ciphertext`, the producer-side datagram retention map, `GroupWindow`, `set_subscribe`, `datagram_payload_limit`, `varint_len`, and the `catalog` module. Keep `TrackKey`, `protect`, `open`, and `nonce` crate-private. -- `track::Producer` allocates sequences monotonically and refuses `create_group` or `insert_datagram` below the next sequence with `Reuse`; that is the only reuse rule. Frames are numbered by write order. The datagram plaintext cap is the constant `MAX_DATAGRAM_PLAINTEXT` (1160). +- Clones of a generation share per-track publication claims. Reopening the same physical track must not reset its nonce counters; mint a fresh epoch for a new publisher instance. Keep this state inside the generation, never in a process-global registry. +- `track::Producer` allocates sequences monotonically and refuses `create_group` or `insert_datagram` below the next sequence with `Reuse` within the claimed track. Frames are numbered by write order. The datagram plaintext cap is the constant `MAX_DATAGRAM_PLAINTEXT` (1160). - `track::Consumer` keeps the datagram sliding window as a 1024-bit bitmask below the greatest opened sequence and marks only after a successful open. Preserve the receive limits and terminal-state behavior covered by [Receive failure](/quest/m2/e2ee/receiver-failure.md); that implementation-only fix is independently landable. - Swap `include_str!("../../../drafts/moq-e2ee-01.json")` for the `-00` vectors, then delete `drafts/moq-e2ee-01.json` and `drafts/moq-e2ee-01.ts`; `just drafts check` globs the remaining generator. Keep the lifecycle tests the draft requires: monotonic allocation, exhaustion with failed opens counted, bounded datagram suppression, and a new epoch authenticating while the old keys do not. -- Update `doc/lib/rs/index.md`, the crate README, and the changelog to the new surface. No wire change; the draft already carries the profile. +- Update `doc/lib/rs/index.md`, the crate README, and the changelog to the new surface. Keep raw key material, HKDF helpers, catalog policy, and duplicate aliases out of the public exports; expose only the limits applications use to size payloads. + +Public API: breaking reshape of moq-e2ee 0.0.1. Wire/interoperability: the +implemented `moq-e2ee-01` numeric generation and HKDF labels become the +draft's `moq-e2ee-00` epoch-based derivation, changing names, keys, and +ciphertext compatibility. Do not describe this as no wire change merely +because the target draft already exists. Verify publication status before +implementation and preserve any actually published profile as required by +the repository's wire-compatibility rule. ## Required -- [Opaque broadcast path](/quest/m2/e2ee/path.md) - settles the path derivation and vectors this crate implements +- [Opaque broadcast path](/quest/m0/e2ee-path.md) - settles the path derivation and vectors this crate implements ## Related diff --git a/quest/m2/e2ee/path.md b/quest/m0/e2ee-path.md similarity index 100% rename from quest/m2/e2ee/path.md rename to quest/m0/e2ee-path.md diff --git a/quest/m2/reuseport-group.md b/quest/m0/sock-group.md similarity index 62% rename from quest/m2/reuseport-group.md rename to quest/m0/sock-group.md index 44d9bc6ad9..50591fbb2d 100644 --- a/quest/m2/reuseport-group.md +++ b/quest/m0/sock-group.md @@ -11,9 +11,9 @@ socket drop are unrepresentable rather than documented. Today the rule is left to the caller (rs/moq-sock/src/shard.rs:141-153): the kernel numbers a reuseport group by what is in it, so closing one socket -renumbers every member after it and the filter steers their traffic to the -wrong sockets. `Member::bind` (:252-273) enforces bind order and hands the -socket straight back, and the filter goes on only when the last member binds +moves the last socket into the removed slot and invalidates existing +connection-ID steering for that socket. `Member::bind` (:252-273) enforces +bind order and hands the socket straight back, and the filter goes on only when the last member binds (:344-349), so: - binding fewer than all declared members exposes usable sockets before the @@ -35,6 +35,13 @@ which is why this has not bitten. It is still a hole in the public API. The runtime-side consumer of this shape is [#2964](/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md). -## Required +Keep `moq_tokio::bind` and its current names. The re-export is useful to +existing relay callers and does not require exposing the group's formation +state. Migrate affected in-tree consumers in the API change so they compile; +[#2964](/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md) +owns any remaining replacement of tokio's redundant internal bookkeeping. -- [Merge dev](/quest/m1/merge-dev.md) - builds on dev-only code that reaches `main` with the merge +Public API: breaking completed-member ownership in moq-sock 0.0.1. Preserve +the published moq-tokio worker signatures. Wire: no format change. Correct +the socket-removal explanation in source comments and docs inline, and run +the formation and drop regressions on Linux CI. diff --git a/quest/m0/uring-identity.md b/quest/m0/uring-identity.md new file mode 100644 index 0000000000..be9d0d412c --- /dev/null +++ b/quest/m0/uring-identity.md @@ -0,0 +1,44 @@ +# [M] uring I/O carries its worker and steering identity + +## Goal + +An endpoint cannot drive a socket through the wrong worker or issue connection +IDs for a different reuseport member. WebTransport setup inherits the worker +that already drives its connection. Keep the worker, UDP, and QUIC layers. + +## Plan + +`Handle::udp` already associates a socket with a worker, but `Endpoint::new` +takes a separate Handle and `endpoint::Config::shard` is independently +settable. A socket from worker A combined with handle B splits receive I/O +and driver progress across unrelated loops. A wrong shard steers replies to +the wrong socket. `web::Request::accept` repeats the independently supplied +handle next to a connection that already has an owner. + +Derive worker identity from the owned socket/connection and carry steering +identity from the completed socket-group member. A plain unsharded socket +remains supported. Remove redundant independently supplied identity rather +than documenting combinations callers must remember. Preserve weak lifetime +links where needed so an I/O handle does not keep a stopped worker alive. + +Update the single-connection helpers and in-tree runtime callers with the +same rule. Keep root Config and the existing TxBuf name; those names do not +cause the mismatch. Document exported metrics fields as part of the API pass. + +Linux CI covers a stopped owner, two workers on the same thread, a two-member +steered listener, and WebTransport setup. Invalid combinations are either +unconstructible through the public API or refused before tasks or I/O start. +Normal single-worker and multi-worker traffic still works. Preserve the +published moq-tokio worker surface when migrating its internal plumbing. + +Public API: breaking construction/configuration changes in moq-uring 0.0.1 +and the shared moq-sock member integration. Wire: no format change. + +## Required + +- [Socket group](/quest/m0/sock-group.md) - supplies completed members with retained socket ownership + +## Related + +- [Handshake cancellation](/quest/m2/uring-handshake-cancel.md) - cleanup during a suspended handshake without another API +- [One QUIC backend](/quest/m1/quic-one-backend.md) - the existing plan owns backend feature removal diff --git a/quest/m1/README.md b/quest/m1/README.md index 1341080bf6..07ed79b7c5 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -12,8 +12,9 @@ allocator mirrors, the bindings), the merge gates, and the merge itself. Branch a quest from `dev` when it breaks a published API or wire. A quest stays here only if it breaks a published API or wire, or gates the merge; [Merge dev](/quest/m1/merge-dev.md) names the gates, and [Release](/quest/m1/release.md) -names what gates the release after it. Work that is identical -on `main`, additive, or targets a `0.0.x` crate lives in +names what gates the release after it. The immediate 0.0.x API audit lives +in [m0](/quest/m0/README.md). Other work that is identical on `main`, additive, +or targets a `0.0.x` crate lives in [m2](/quest/m2/README.md) even when it builds on dev-only code; it starts on `main` after the merge. The four 0.0.x media crates are the explicit exception: their pre-0.1 contracts live in [m0](/quest/m0/README.md) and can land on main diff --git a/quest/m1/release.md b/quest/m1/release.md index f101feb1c9..84e0190f99 100644 --- a/quest/m1/release.md +++ b/quest/m1/release.md @@ -69,8 +69,10 @@ expiry (#3647); publish robustness (Firefox hardware encoding, file demux, relay embedding and the LAN mesh (#3638, #3648, #3621, #3587); one auth contract with leases (#3688, #3739); data tracks and captions (#3109, #3640); one `Connection` with URL replacement (#3614, #3636); first-hop resume and the shared send estimate -(#3312, #3616). moq-e2ee ships as it is on dev; the -[E2EE](/quest/m2/e2ee/README.md) questline owns its twin and interop. +(#3312, #3616). The [0.0.x API gate](/quest/m0/README.md) settles archive, +E2EE, sock, and uring before release. Its E2EE reshape changes the implemented +profile and derived names/keys; report that interoperability change explicitly. +The [E2EE](/quest/m2/e2ee/README.md) questline owns its twin and interop. The soak bullet below is cleared by hand: the merged relay serves moq.pro staging with `/metrics` watched and a fresh viewer joining a days-old @@ -81,6 +83,7 @@ Public API: none beyond the required quests. Wire: none. ## Required +- [0.0.x API gate](/quest/m0/README.md) - the four audited crates expose the intended release contracts - [Merge dev](/quest/m1/merge-dev.md) - the tree the release is cut from - [Binding audio tests](/quest/m2/binding-audio-tests.md) - every binding proves the audio config it exposes - [Decode format](/quest/m2/ffi-decode-format.md) - the C-only decode knob reaches every uniffi binding diff --git a/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md b/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md index 31479c3585..184f777111 100644 --- a/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md +++ b/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md @@ -14,7 +14,7 @@ that API redesign here. This follow-up replaces the worker's private lifetime bookkeeping with the hardened socket-group primitive after dev merges. Adopt the claims and complete-group ownership supplied by -[Reuseport group formation](/quest/m2/reuseport-group.md). No member serves +[Reuseport group formation](/quest/m0/sock-group.md). No member serves before the final bind and filter attachment, and every socket stays owned until serving has stopped for the group. Preserve the M1 owner, shutdown, failure propagation, and worker-local builder contract without another @@ -32,7 +32,7 @@ both the M1 ownership proof and this integration are complete. ## Required -- [Reuseport group formation](/quest/m2/reuseport-group.md) - complete formation and retained sockets in the shared primitive +- [Reuseport group formation](/quest/m0/sock-group.md) - complete formation and retained sockets in the shared primitive - [Merge dev](/quest/m1/merge-dev.md) - the released ownership surface is available on main ## Closes diff --git a/quest/m2/README.md b/quest/m2/README.md index 4ad8caabe4..544e5fd23d 100644 --- a/quest/m2/README.md +++ b/quest/m2/README.md @@ -62,7 +62,6 @@ before format-specific metadata. Unrelated areas can proceed in parallel. - [Binding audio tests](/quest/m2/binding-audio-tests.md) - every binding proves the Opus frame duration and throwing setters it exposes, and smoke-full publishes audio with an explicit config - [Decode format](/quest/m2/ffi-decode-format.md) - the C-only decode pixel format knob reaches every uniffi binding - [JSON mutate](/quest/m2/json-mutate.md) - Rust gains the closure edit JS already has, beside the guard -- [0.0.x crate names](/quest/m2/api-unreleased-crate-names.md) - moq-sock, moq-uring, moq-archive, and moq-e2ee follow the naming rules after the merge - [Publisher clocks](/quest/m2/publisher-clock.md) - wire the shared clock through native and browser publisher restarts - [Broadcast route](/quest/m2/js-broadcast-route.md) - JS Announce.Broadcast goes live on any claim matching its path, like Rust routed() - [io_uring check](/quest/m2/check-uring-feature.md) - a moq-relay diff compiles the io-uring feature in `just check`, not only nightly @@ -118,7 +117,6 @@ before format-specific metadata. Unrelated areas can proceed in parallel. - [Perf](/quest/m2/perf/README.md) - eliminate measured hot-path costs across moq-uring, kio, and the moq-net model - [#2924](/quest/m2/2924-moq-relay-tls-rotation-is-not-atomic-across-thread-per.md) - every QUIC worker shares one reloadable served identity, so rotation is atomic and generate works with workers - [#2964](/quest/m2/2964-quic-workers-dropping-one-split-server-resizes-the.md) - integrate the M1 worker owner with hardened socket-group formation -- [Reuseport group](/quest/m2/reuseport-group.md) - `moq_sock::shard::Group` exposes no socket before the filter is attached and retains all of them - [Safari WebTransport](/quest/m2/safari-webtransport.md) - WebKit browsers return to WebTransport once WebKit 319818 ships fixed - [Audio quality harness](/quest/m2/audio-quality-harness/README.md) - a playout latency regression fails a run instead of arriving as a bug report - [Latency ledger](/quest/m2/latency-ledger.md) - a session reports where its end-to-end audio delay went, stage by stage diff --git a/quest/m2/api-unreleased-crate-names.md b/quest/m2/api-unreleased-crate-names.md deleted file mode 100644 index 8227e7adbe..0000000000 --- a/quest/m2/api-unreleased-crate-names.md +++ /dev/null @@ -1,32 +0,0 @@ -# [S] The 0.0.x crates follow the naming rules - -## Goal - -moq-sock, moq-uring, moq-archive, and moq-e2ee, all `0.0.x` with in-tree -consumers only, expose short role names under their modules and nothing -that is private in spirit. They do not gate the release. - -## Plan - -- moq-sock: `bind::Udp` becomes `bind::udp::Options` and - `bind::udp_is_dual_stack` becomes `bind::udp::is_dual_stack`. -- moq-uring: `udp::TxBuf` becomes `udp::Staged`; the root `Config` re-export - of `worker::Config` goes, since `udp`, `quic::endpoint`, `quic::client`, - and `quic::server` each have their own; the `metrics::Snapshot` fields get - doc lines and the crate turns on `missing_docs`. -- moq-archive: `store::List` / `Listed` become `store::list::{Query, Entry}`; - the `path::{groups_prefix, segments_prefix, groups_offset}` duplicates of - the `Store` methods become `pub(crate)`; `check_id` leaves the crate root; - `info`, `path`, `segment`, and `store` get module docs. -- moq-e2ee: the crate-root re-exports of `datagram_payload_limit`, `MAX_U32`, - `MAX_U53`, `varint_len`, and the raw-key `protect` / `open` / `nonce` go - private; `TrackKey` becomes `track::Key`; the `DatagramEvent` alias goes; - `catalog::protect_deflate` / `open_deflate` fold into `protect` / `open` - keyed off the semantic name or a `Compression` argument. - -Public API: breaking on four `0.0.x` crates; lands on main after the merge. -Wire: none. - -## Required - -- [Merge dev](/quest/m1/merge-dev.md) - the crates exist only on dev diff --git a/quest/m2/e2ee/README.md b/quest/m2/e2ee/README.md index e2081c989c..2527e38437 100644 --- a/quest/m2/e2ee/README.md +++ b/quest/m2/e2ee/README.md @@ -19,7 +19,7 @@ The contract is [draft-lcurley-moq-e2ee](/drafts/draft-lcurley-moq-e2ee.md), pro ### Epoch and identity -- Every publisher instance mints an epoch, a UUIDv7 in lowercase text, and publishes at `/`, where `` derives from the credential and the semantic broadcast name without the epoch ([Opaque broadcast path](/quest/m2/e2ee/path.md)). The epoch is an input to every HKDF derivation, so a restart, takeover, or explicit group sequence cannot repeat a nonce under a key: nothing is persisted across instances and no generation counter is redistributed. Subscribers discover instances under the `/` prefix and take the greatest epoch, which sorts newest; a known full path carries its epoch in the last segment. +- Every publisher instance mints an epoch, a UUIDv7 in lowercase text, and publishes at `/`, where `` derives from the credential and the semantic broadcast name without the epoch ([Opaque broadcast path](/quest/m0/e2ee-path.md)). The epoch is an input to every HKDF derivation, so a restart, takeover, or explicit group sequence cannot repeat a nonce under a key: nothing is persisted across instances and no generation counter is redistributed. Subscribers discover instances under the `/` prefix and take the greatest epoch, which sorts newest; a known full path carries its epoch in the last segment. - The epoch is untrusted and unauthenticated. A wrong epoch fails authentication and a withheld one denies service; neither can make a nonce repeat, because only the publisher instance chooses what it encrypts under. This is the same trust a cache needs to serve the right instance, and it is deliberately e2ee-only: plaintext hang keeps its current paths. - Nothing in the path says the bytes are encrypted. The format after decryption (`meeting.hang`) is inside the opaque name; a plaintext player, exporter, or matcher that opens a protected broadcast finds no catalog it can read and fails with its usual typed refusal, the same as for any format it does not support. - One 32-byte secret authorizes the whole broadcast; HKDF-SHA-256 derives separate AES-128-GCM keys for each physical track and for grouped-frame versus datagram domains. A grouped frame uses the 96-bit nonce `uint64_be(group) || uint32_be(frame)`; a datagram uses its sequence with frame zero under the datagram domain. Empty AAD: every immutable end-to-end field is in the HKDF info or the nonce. @@ -30,8 +30,8 @@ The contract is [draft-lcurley-moq-e2ee](/drafts/draft-lcurley-moq-e2ee.md), pro The Rust and TypeScript cores expose the same surface, and nothing else: -- `Credential { context, kid, secret }` is what the application distributes. It is cheap to clone, never serializes the secret, and redacts it from `Debug`. -- `credential.generation(epoch)` binds a discovered or minted epoch. `Generation` owns `name(semantic)` for opaque names (any string, tracks or path segments alike), `produce(track)` and `consume(track)` for protected `moq-net` tracks, and a `mint()` helper that returns a fresh UUIDv7. +- `Credential { context, kid, secret }` accepts the application-owned secret. The application generates and distributes it over its authenticated channel; the library does not mint a secret it cannot return. Credential is cheap to clone, never serializes the secret, and redacts it from `Debug`. +- `credential.generation(epoch)` binds a discovered or minted epoch. `Generation` owns `name(semantic)` for opaque names (any string, tracks or path segments alike), `produce(track)` and `consume(track)` for protected `moq-net` tracks, and `Epoch::mint()` returns a fresh UUIDv7. Clones share publisher claims so reopening a track cannot reset its nonce counters. - `track::Producer` appends groups and datagrams and allocates identities; `track::Consumer` yields groups and datagram events. `group::Producer` and `group::Consumer` wrap the whole group lifecycle so every AEAD call has the canonical physical name and transport identity. - Errors are the draft's typed codes plus the transport's. Nothing catalog-, hang-, or MSF-shaped lives here: a catalog is a track under a derived name, and compression is the catalog owner's job. - Stateless `seal`/`open` primitives with caller-chosen identities, HKDF labels and info builders, raw key bytes, and process-global claims are not public. The vectors are tested inside each core. @@ -45,11 +45,6 @@ The Rust and TypeScript cores expose the same surface, and nothing else: ## Quests - [Receive failure](/quest/m2/e2ee/receiver-failure.md) - failed opens consume the key budget and a bad grouped frame wakes and terminates every pending read -- [Opaque broadcast path](/quest/m2/e2ee/path.md) - the draft, vectors, - and questline publish at `/` with no `.e2ee` suffix -- [Rust E2EE core on moq-e2ee-00](/quest/m2/e2ee/rust.md) - reshape the merged - `moq-e2ee` crate to the epoch profile and the shared library surface, and retire - the `moq-e2ee-01` vectors - [TypeScript E2EE core](/quest/m2/e2ee/typescript.md) - the `@moq/e2ee` package mirroring the Rust surface, with WebCrypto in a serial pump - [Rust protected publisher seams](/quest/m2/e2ee/rust-publish.md) - Rust media @@ -72,5 +67,7 @@ The Rust and TypeScript cores expose the same surface, and nothing else: ## Related +- [Release E2EE API](/quest/m0/e2ee-api.md) - the Rust surface and epoch profile are release requirements; browser and application integration follow here +- [Opaque broadcast path](/quest/m0/e2ee-path.md) - the draft and vectors the cores implement - [archive](/quest/m2/archive/README.md) - protected broadcasts are deliberately outside recording and replay formats - [Merge dev](/quest/m1/merge-dev.md) - its HLS soak (a fresh viewer joining a days-old broadcast, playable since #3240) covers plaintext broadcasts only; stock HLS and DASH cannot read a protected catalog diff --git a/quest/m2/e2ee/receiver-failure.md b/quest/m2/e2ee/receiver-failure.md index eb0ffc5947..967fffc0b4 100644 --- a/quest/m2/e2ee/receiver-failure.md +++ b/quest/m2/e2ee/receiver-failure.md @@ -29,4 +29,4 @@ do not add exported test hooks. ## Related -- [Rust E2EE core](/quest/m2/e2ee/rust.md) - API and profile alignment must preserve these receive invariants +- [Rust E2EE core](/quest/m0/e2ee-api.md) - API and profile alignment must preserve these receive invariants diff --git a/quest/m2/e2ee/typescript.md b/quest/m2/e2ee/typescript.md index 699a59763b..4bbf077a33 100644 --- a/quest/m2/e2ee/typescript.md +++ b/quest/m2/e2ee/typescript.md @@ -17,4 +17,4 @@ the Rust crate name for name, without putting keys or crypto policy in `@moq/net ## Required -- [Rust E2EE core on moq-e2ee-00](/quest/m2/e2ee/rust.md) - settles the surface this package mirrors and retires the `-01` vectors +- [Rust E2EE core on moq-e2ee-00](/quest/m0/e2ee-api.md) - settles the surface this package mirrors and retires the `-01` vectors diff --git a/quest/m2/one-port/README.md b/quest/m2/one-port/README.md index 3afff923ab..1507e709be 100644 --- a/quest/m2/one-port/README.md +++ b/quest/m2/one-port/README.md @@ -81,5 +81,5 @@ pre-accepted streams can stand behind. - [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 -- [Reuseport group](/quest/m2/reuseport-group.md) - the shard formation a per-shard demux rides +- [Reuseport group](/quest/m0/sock-group.md) - the shard formation a per-shard demux rides - [Cluster discovery flags](/quest/m2/cluster-flags.md) - the flag shape for anything the demux adds From 4e075a9fd329da61f3e6f17776185972ed7cad1d Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 20 Sep 2026 17:51:50 -0700 Subject: [PATCH 3/5] docs(quest): isolate release API prerequisites Document the audited package boundaries and require only the six release API quests, not unrelated m0 tracks. Co-Authored-By: GPT-5 --- quest/m1/README.md | 6 +++--- quest/m1/release.md | 16 +++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/quest/m1/README.md b/quest/m1/README.md index 07ed79b7c5..8e04209164 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -12,9 +12,9 @@ allocator mirrors, the bindings), the merge gates, and the merge itself. Branch a quest from `dev` when it breaks a published API or wire. A quest stays here only if it breaks a published API or wire, or gates the merge; [Merge dev](/quest/m1/merge-dev.md) names the gates, and [Release](/quest/m1/release.md) -names what gates the release after it. The immediate 0.0.x API audit lives -in [m0](/quest/m0/README.md). Other work that is identical on `main`, additive, -or targets a `0.0.x` crate lives in +names what gates the release after it. The immediate 0.0.x release API audit +lives within [m0](/quest/m0/README.md). Other work that is identical on `main`, +additive, or targets a `0.0.x` crate lives in [m2](/quest/m2/README.md) even when it builds on dev-only code; it starts on `main` after the merge. The four 0.0.x media crates are the explicit exception: their pre-0.1 contracts live in [m0](/quest/m0/README.md) and can land on main diff --git a/quest/m1/release.md b/quest/m1/release.md index 84e0190f99..08cac7aa1c 100644 --- a/quest/m1/release.md +++ b/quest/m1/release.md @@ -69,10 +69,11 @@ expiry (#3647); publish robustness (Firefox hardware encoding, file demux, relay embedding and the LAN mesh (#3638, #3648, #3621, #3587); one auth contract with leases (#3688, #3739); data tracks and captions (#3109, #3640); one `Connection` with URL replacement (#3614, #3636); first-hop resume and the shared send estimate -(#3312, #3616). The [0.0.x API gate](/quest/m0/README.md) settles archive, -E2EE, sock, and uring before release. Its E2EE reshape changes the implemented -profile and derived names/keys; report that interoperability change explicitly. -The [E2EE](/quest/m2/e2ee/README.md) questline owns its twin and interop. +(#3312, #3616). The six m0 release API quests settle archive, E2EE, sock, and +uring before release without making the independent Pronto or media tracks a +release prerequisite. The E2EE reshape changes the implemented profile and +derived names/keys; report that interoperability change explicitly. The +[E2EE](/quest/m2/e2ee/README.md) questline owns its twin and interop. The soak bullet below is cleared by hand: the merged relay serves moq.pro staging with `/metrics` watched and a fresh viewer joining a days-old @@ -83,7 +84,12 @@ Public API: none beyond the required quests. Wire: none. ## Required -- [0.0.x API gate](/quest/m0/README.md) - the four audited crates expose the intended release contracts +- [E2EE path](/quest/m0/e2ee-path.md) - settle the unpublished opaque path derivation and vectors +- [E2EE API](/quest/m0/e2ee-api.md) - expose epoch-scoped ownership and align the implemented profile +- [Socket group](/quest/m0/sock-group.md) - make partial reuseport groups and early socket drops unrepresentable +- [uring identity](/quest/m0/uring-identity.md) - bind sockets, connections, workers, and steering identity together +- [Archive ranges](/quest/m0/archive-ranges.md) - use one validated inclusive range contract +- [Archive listing](/quest/m0/archive-listing.md) - expose one recording-scoped listing query - [Merge dev](/quest/m1/merge-dev.md) - the tree the release is cut from - [Binding audio tests](/quest/m2/binding-audio-tests.md) - every binding proves the audio config it exposes - [Decode format](/quest/m2/ffi-decode-format.md) - the C-only decode knob reaches every uniffi binding From 6382afbf8fbfff73cc3074d858b4dfcd39fa489a Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 20 Sep 2026 18:59:20 -0700 Subject: [PATCH 4/5] docs(quest): gate the PathPrefixes API review Keep every remaining api-* quest in the dev review outcome list before release. Co-Authored-By: GPT-5 --- quest/m1/api-review-gate.md | 1 + 1 file changed, 1 insertion(+) diff --git a/quest/m1/api-review-gate.md b/quest/m1/api-review-gate.md index efd1ced990..fc61463d85 100644 --- a/quest/m1/api-review-gate.md +++ b/quest/m1/api-review-gate.md @@ -17,6 +17,7 @@ file is deleted on completion) or deletes the quest with a note in quest is deleted too. No code. The list: [Bindings announce match](/quest/m1/api-origin-scopes.md), +[PathPrefixes](/quest/m1/api-path-prefixes.md), [Rendition ownership](/quest/m1/api-mux-rendition.md). ## Related From 586a90b3544f0b21ece617f0cf47242d60e7296a Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Sun, 20 Sep 2026 18:59:52 -0700 Subject: [PATCH 5/5] docs(quest): link the landed QUIC backend work Keep the uring identity relationship after the completed backend quest was retired. Co-Authored-By: GPT-5 --- quest/m0/uring-identity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quest/m0/uring-identity.md b/quest/m0/uring-identity.md index be9d0d412c..4373087c6a 100644 --- a/quest/m0/uring-identity.md +++ b/quest/m0/uring-identity.md @@ -41,4 +41,4 @@ and the shared moq-sock member integration. Wire: no format change. ## Related - [Handshake cancellation](/quest/m2/uring-handshake-cancel.md) - cleanup during a suspended handshake without another API -- [One QUIC backend](/quest/m1/quic-one-backend.md) - the existing plan owns backend feature removal +- [PR #3811](https://github.com/moq-dev/moq/pull/3811) - landed the single noq backend before this ownership work