From 903e3410fd1a1f10d1e6ea5a5d65ce2172877c48 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 10:05:58 -0700 Subject: [PATCH 1/8] quest: plan follow-ups from the 2026-10-15 merge batch Add quests for the Kotlin destroyed-argument abort, the TS same-instance resume fixture, the seeded BBR marking test, C++ reuse of the shared FFI build, the Nightly check after FFI reuse, an opaque catalog consumer, and the two auth steps after #5148. Rewrite read-backfill to take its floor from reported bounds instead of a settlement window, and record the confirmed TS compliance cause. Co-Authored-By: Claude Opus 5.5 --- quest/m0/ci/README.md | 1 + quest/m0/ci/nightly-ffi-reuse.md | 29 ++++++++++ quest/m1/README.md | 3 ++ quest/m1/auth/README.md | 2 + quest/m1/auth/request-auth-id.md | 48 +++++++++++++++++ quest/m1/auth/request-update.md | 39 ++++++++++++++ quest/m1/catalog-consumer-opaque.md | 21 ++++++++ quest/m1/cpp-ffi-reuse.md | 33 ++++++++++++ quest/m1/kt-destroyed-arg.md | 38 +++++++++++++ quest/m1/read-backfill.md | 54 ++++++++++--------- quest/m1/test-flakes-2/README.md | 2 + quest/m1/test-flakes-2/bbr-marking-seed.md | 31 +++++++++++ .../m1/test-flakes-2/ts-resume-late-frames.md | 32 +++++++++++ quest/m1/test-flakes-2/ts-video-content.md | 18 +++++++ 14 files changed, 325 insertions(+), 26 deletions(-) create mode 100644 quest/m0/ci/nightly-ffi-reuse.md create mode 100644 quest/m1/auth/request-auth-id.md create mode 100644 quest/m1/auth/request-update.md create mode 100644 quest/m1/catalog-consumer-opaque.md create mode 100644 quest/m1/cpp-ffi-reuse.md create mode 100644 quest/m1/kt-destroyed-arg.md create mode 100644 quest/m1/test-flakes-2/bbr-marking-seed.md create mode 100644 quest/m1/test-flakes-2/ts-resume-late-frames.md diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index 27fbcd747e6..5c268f88654 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -57,3 +57,4 @@ Measure each change against the numbers above and record the result in its PR. - [Require platform gates](/quest/m0/ci/platform-queue.md) - activate the consolidated platform checks in the main and release rulesets after the workflow lands - [mbx on R2](/quest/m0/ci/mbx-r2.md) - Rust builds on every OS read an R2 cache that only `main` writes, escaping the 10 GB Actions cache - [Nix cache](/quest/m0/ci/nix-cache.md) - `nix.moq.dev` on R2 replaces Cachix for CI and releases +- [Nightly after FFI reuse](/quest/m0/ci/nightly-ffi-reuse.md) - condition: the first scheduled Nightly and `moq-ffi-v*` tag after #5549; measure them and verify the shared build diff --git a/quest/m0/ci/nightly-ffi-reuse.md b/quest/m0/ci/nightly-ffi-reuse.md new file mode 100644 index 00000000000..389724650ac --- /dev/null +++ b/quest/m0/ci/nightly-ffi-reuse.md @@ -0,0 +1,29 @@ +# [XS] Nightly after FFI reuse + +## Goal + +Condition: a scheduled Nightly and the first `moq-ffi-v*` tag have completed +since [#5549](https://github.com/moq-dev/moq/pull/5549) (shared FFI build) +and [#5527](https://github.com/moq-dev/moq/pull/5527) (independent Nightly +workloads) landed. Check with `gh run list --workflow nightly.yml --event schedule` +and the FFI release runs for the tag. Then measure and verify the items +below, fix or quest what fails, and delete this quest. + +## Plan + +From the Nightly run: + +- Duration of each workload job and total runner-minutes, against the + sampled numbers in `.github/ffi.md`. +- Every benchmark still runs, including the timebase and audio benchmarks + that landed beside the split. +- No workload fails, and the Rust group fits its existing limit. +- Size-history keys are stable across the change. + +From the first `moq-ffi-v*` release: + +- The nested coordinator job names match what the publisher check looks up. +- The artifact patterns select the right libraries for each package. +- The Swift wrapper dry run passes under the release's permissions. + +Don't dispatch Nightly on a branch to get these; wait for the scheduled run. diff --git a/quest/m1/README.md b/quest/m1/README.md index c40699aa328..19b83345aa3 100644 --- a/quest/m1/README.md +++ b/quest/m1/README.md @@ -85,6 +85,7 @@ above them. - [Live largest location](/quest/m1/largest.md) - `largest()` replaces `latest()` in Rust and JS as a live stream of the track's largest location, known before the first group from the publisher's report - [Live TS clock validation](/quest/m1/ts-live-clock.md) - resolve the local PCR-rate failure with reproducible timing evidence and CI controls - [Binding panic supervision](/quest/m1/ffi-panic-supervision.md) - a panic in any moq-c or moq-ffi background task reaches the host as a panic, never a lost callback or Cancel +- [Kotlin destroyed arguments](/quest/m1/kt-destroyed-arg.md) - a destroyed object passed to a generated Kotlin call throws instead of aborting the process - [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 - [Re-measure planned wire-compat breaks](/quest/m1/compat-remeasure.md) - each release re-measures and re-pins or deletes its planned breaks, so the scheduled wire-compat check stays green across releases - [Plan: a public model](/quest/m1/model-public-surface.md) - every crate-private model item is made public or unnecessary, so a custom publisher or subscriber builds from the public model alone @@ -163,10 +164,12 @@ above them. - [Malformed moq-transport input](/quest/m1/ietf-malformed-close.md) - malformed draft-18 and draft-21 control input closes the session with the draft's code, or PROTOCOL_VIOLATION where a code is a real burden and the fallback is recorded in `doc/concept/standard.md`, in moq-net and js/net - [Binding track resolver](/quest/m1/binding-track-helper.md) - binding apps subscribe to a rendition without inlining `track` or the key - [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 +- [Opaque catalog consumer](/quest/m1/catalog-consumer-opaque.md) - `moq_mux::catalog::Consumer` hides its encoding variants and closed state - [Stats prefix tracks](/quest/m1/stats-prefix-tracks.md) - additive tracks required by the breaking lag API; existing maps remain supported - [C++ and OBS package proof](/quest/m1/cpp-package-check.md) - the installed artifacts pass before their first release +- [C++ FFI reuse](/quest/m1/cpp-ffi-reuse.md) - C++ packages link the shared default-feature FFI build once it is proven compatible ## Related diff --git a/quest/m1/auth/README.md b/quest/m1/auth/README.md index 45ca309ed22..ca501555e89 100644 --- a/quest/m1/auth/README.md +++ b/quest/m1/auth/README.md @@ -112,6 +112,8 @@ because `main` assigned 0x3A to NOT_FETCHABLE first. - [Request tokens](/quest/m1/auth/request-token.md) - an experimental Rust verifier hook confines a token's authority to its request and renews it +- [Live requests follow their auth](/quest/m1/auth/request-update.md) - a route or subscription swaps its auth and the session sends the update, keeping the request up +- [Requests name their auth](/quest/m1/auth/request-auth-id.md) - lite-07 AUTH carries an ID and each request names the auth that admits it - [Request gate](/quest/m1/auth/request-gate.md) - every incoming request is refused up front and held to the grant through a gate its dispatcher owns - [Expired token error](/quest/m1/auth/expired-error.md) - an expired token diff --git a/quest/m1/auth/request-auth-id.md b/quest/m1/auth/request-auth-id.md new file mode 100644 index 00000000000..e70700a03d7 --- /dev/null +++ b/quest/m1/auth/request-auth-id.md @@ -0,0 +1,48 @@ +# [M] Requests name their auth on the lite wire + +## Goal + +In `moq-lite-07-wip`, AUTH carries an `ID (i)` the opener chooses +(increasing, never reused), and SUBSCRIBE, SUBSCRIBE_UPDATE, FETCH, +ANNOUNCE_REQUEST and ANNOUNCE_START carry `Auth (i)`: 0 for unnamed, +otherwise the ID plus 1. A named request is admitted only by that auth's +grant and ends when the auth ends or narrows away from it. The presenter +names auths automatically. The `draft-lcurley-moq-auth` extension mirrors it +for moq-transport, additively. + +## Plan + +Step 2 of the unified auth model settled on +[#5148](https://github.com/moq-dev/moq/pull/5148) on 2026-10-15. Step 1 +(#5148, [Request tokens](/quest/m1/auth/request-token.md)) makes a +moq-transport request token an auth on the shared `requests()` queue. + +- ✅ Decided 2026-10-15 (maintainer): one kind of auth. Each request names at + most one (`auth=id`); moq-transport's per-request token is only an encoding + of that model, never a separate API. +- ✅ Decided 2026-10-15 (maintainer): an unnamed request is admitted by the + union of session-lifetime auths only, so a request-lifetime token never + widens tokenless requests. +- ✅ Decided 2026-10-15 (maintainer): a named request outside its auth's grant + is refused, even when session scope would admit it, and it ends with its + auth. +- ✅ Decided 2026-10-15 (maintainer): spec it in lite-07-wip now, before the + cut, so it doesn't cost lite-08. +- A request naming an ID that hasn't arrived yet waits for it while holding + its slot; one naming an ID that already ended is refused UNAUTHORIZED. +- Renewal on lite: add a new auth, SUBSCRIBE_UPDATE the request to it, drop + the old one. +- moq-transport: AUTH's Request ID becomes the auth id, and a new request + parameter names it. The alias types map as: USE_VALUE registers a + request-lifetime auth and names it; REGISTER registers a session-lifetime + auth; USE_ALIAS names one; DELETE withdraws one. +- Update `drafts/draft-lcurley-moq-lite.md` and + `drafts/draft-lcurley-moq-auth.md`, Rust and `js/net`, and run + `just test interop --all`. + +Public API: none beyond step 1's `requests()` and `add()`; naming is +automatic. Wire: lite-07-wip AUTH and five request messages gain fields. + +## Required + +- [Request tokens](/quest/m1/auth/request-token.md) - step 1: request tokens become auths on the shared acceptor queue diff --git a/quest/m1/auth/request-update.md b/quest/m1/auth/request-update.md new file mode 100644 index 00000000000..8f0be4f7220 --- /dev/null +++ b/quest/m1/auth/request-update.md @@ -0,0 +1,39 @@ +# [M] Live requests follow their auth + +## Goal + +A publisher or subscriber renews the credential behind a live request +without reopening it. A `Route` or `Subscription` holds an optional +`moq_net::Auth`; when the application swaps it, the session sends +REQUEST_UPDATE (moq-transport) or SUBSCRIBE_UPDATE (lite) naming the new +auth, and the request stays up. The first consumer is a client that keeps a +PUBLISH_NAMESPACE alive past its credential's expiry by sending +REQUEST_UPDATE with a fresh token before it expires. + +## Plan + +Step 3 of the unified auth model settled on +[#5148](https://github.com/moq-dev/moq/pull/5148) on 2026-10-15, ranked right +after step 1 because it has a consumer now. + +- ✅ Decided 2026-10-15 (maintainer): general request updates, not a + per-request token setter. Each `Route`/`Subscription` carries an + `Option` (name to be settled), and changing it triggers the + update automatically. Unset means the session's scope. +- ✅ Decided 2026-10-15 (maintainer): outgoing tokens go through `add()`; + #5148's `set_request_token` and its renewal sender are not carried forward. +- PUBLISH_NAMESPACE renewal comes first. Per-SUBSCRIBE tokens follow the same + shape but have no consumer yet; include them only if they fall out of the + same code. +- On moq-transport the update carries the token as USE_VALUE; on lite it + names the auth registered through [Requests name their auth](/quest/m1/auth/request-auth-id.md) + once that lands. A refused update ends only that request. +- Settle the type and field names with the maintainer before implementing; + mirror them in `js/net` and the bindings. + +Public API: a new optional auth on routes and subscriptions in Rust and JS. +Wire: none beyond existing REQUEST_UPDATE and SUBSCRIBE_UPDATE. + +## Required + +- [Request tokens](/quest/m1/auth/request-token.md) - step 1: request tokens become auths on the shared acceptor queue diff --git a/quest/m1/catalog-consumer-opaque.md b/quest/m1/catalog-consumer-opaque.md new file mode 100644 index 00000000000..061452d3372 --- /dev/null +++ b/quest/m1/catalog-consumer-opaque.md @@ -0,0 +1,21 @@ +# [S] An opaque catalog consumer + +## Goal + +`moq_mux::catalog::Consumer` is an opaque struct: callers can't match or +construct its encoding variants or its closed state from outside the crate. +Today it is a `#[non_exhaustive]` enum whose doc-hidden `Hang`, `Msf` and +`Closed` variants are still public. + +## Plan + +Raised by Codex on [#5561](https://github.com/moq-dev/moq/pull/5561), which +added the doc-hidden `Closed` state for catalog EOF. + +- ✅ Decided 2026-10-15 (maintainer): make it opaque; a breaking change the + next release absorbs. +- Benchmarks and tests that build a variant directly use `Consumer::new` or a + `From` impl instead. Expose only what a caller needs today, + and question whether `msf::Consumer::track` must stay public. + +Public API: breaking; the enum's variants become private. Wire: none. diff --git a/quest/m1/cpp-ffi-reuse.md b/quest/m1/cpp-ffi-reuse.md new file mode 100644 index 00000000000..a321f37451e --- /dev/null +++ b/quest/m1/cpp-ffi-reuse.md @@ -0,0 +1,33 @@ +# [M] C++ packages reuse the shared FFI build + +## Goal + +The C++ packages link the same default-feature moq-ffi libraries that the +shared FFI build produces for Go, Kotlin and Swift, instead of building their +own, once those libraries are proven compatible on all four C++ targets. +Installed C++ consumers pass with the prebuilt input. The separate `cpp-v*` +release lifecycle stays explicit: a C++ release still names which FFI build +it ships. + +## Plan + +Follow-up to [#5549](https://github.com/moq-dev/moq/pull/5549), which shares +the default FFI build across Go, Kotlin and Swift and records its evidence in +`.github/ffi.md`. The sampled C++ static libraries differed from the reuse +candidates, so C++ was left out. + +- Prove compatibility before changing a build. Compare, per C++ target: + target triple, features, profile, native versus cross host, glibc/CRT + version, and UniFFI metadata. Record the comparison in `.github/ffi.md` + beside the existing ones. A difference that matters ends the quest with + the reason recorded; one that does not gets normalized, as #5549 did for + Windows timestamps. +- Then design a prebuilt input for the CMake and build-script paths, and test + installed consumers against it. +- Keep `cpp-v*` releases separate from `moq-ffi-v*`; the C++ release + selects an FFI build explicitly rather than following the newest one. + +## Related + +- [C++ and OBS package proof](/quest/m1/cpp-package-check.md) - the installed-consumer checks this reuses +- [Nightly after FFI reuse](/quest/m0/ci/nightly-ffi-reuse.md) - confirms the shared build in production first diff --git a/quest/m1/kt-destroyed-arg.md b/quest/m1/kt-destroyed-arg.md new file mode 100644 index 00000000000..af84a878551 --- /dev/null +++ b/quest/m1/kt-destroyed-arg.md @@ -0,0 +1,38 @@ +# [S] Kotlin refuses a destroyed argument instead of aborting + +## Goal + +Passing an already-destroyed moq object as an argument to a generated Kotlin +call throws a Kotlin exception; it never aborts the process. Today a +generated converter clones the argument's native handle without taking its +lifetime guard, so a destroyed argument reaches native code and aborts the +JVM. A regression test at a constructor boundary fails without the fix, and +valid closed-publication behavior is unchanged. + +## Plan + +Found while landing [#5559](https://github.com/moq-dev/moq/pull/5559). + +- ✅ Decided 2026-10-15 (maintainer): fix it in the Kotlin template of the + generator fork moq pins today and bump that pin; do not hand-patch the + generated bindings. The [hard fork](/quest/m1/uniffi-fork.md) carries the + fix when it moves the generators. +- Reproduce first, at a constructor that takes another moq object (for + example a handle built from a broadcast), with the argument destroyed + before the call. Check whether the same converter path serves methods and + free functions, and cover each shape the template emits. +- The guard check belongs where the converter lowers an object argument, the + same place a receiver already checks its own guard. +- Verify that a closed but not destroyed publication still behaves as today: + its calls return the closed error, not the new destroyed one. +- Check whether the other generators moq uses (Swift, Python, Go, Dart, C++) + lower object arguments the same way; fix any that share the bug in the same + pin bump, or record them as follow-ups. + +Public API: a destroyed argument becomes an exception instead of an abort. +Wire: none. + +## Related + +- [Hard-fork the binding generator](/quest/m1/uniffi-fork.md) - inherits the template fix +- [Kotlin JVM exit](/quest/m2/kt-jvm-exit.md) - another native crash in the Kotlin binding diff --git a/quest/m1/read-backfill.md b/quest/m1/read-backfill.md index 724b7b98056..367f34cabaf 100644 --- a/quest/m1/read-backfill.md +++ b/quest/m1/read-backfill.md @@ -6,7 +6,8 @@ The Rust and JS container consumers agree on groups that arrive below the read cursor: neither delivers them. The read stays `Frame | Gap | End` in delivery order past the cursor, and what lies below is already covered by the `Gap` that moved the cursor. This is the ordered read only: APIs that yield in -arrival order keep every group. +arrival order keep every group. Startup adds no latency: the cursor starts +from the bounds the publisher reports, never from a wait. ## Plan @@ -16,32 +17,33 @@ delivers a group that arrives below the cursor as a late backfill; Rust - ✅ Decided 2026-10-13: both drop, matching Rust. Not chosen: both deliver, flagged so readers can skip; leave it open. -- Match Rust's startup first. Groups are served newest-first, so the head of a - retained window can arrive after the live edge; JS sets its cursor from the - first arrival, so dropping alone would lose valid startup media - (`consumer.outoforder.test.ts` covers that order). Rust keeps it by waiting - for an explicit start floor under the max-delay budget - (`startup_keeps_late_arriving_head_group_within_max_delay`); give JS the same - startup so the window head is never below the cursor. -- ✅ Decided 2026-10-13: a settlement window, for ordered APIs only. An - ordered read (the container consumer's in-order `Read` stream, Rust and JS) - with no declared start holds its first delivery until the newest content is - a full max-delay budget ahead (the test Rust already uses to give up on a - missing group), takes the lowest group seen as its floor, then drops - anything below. Unordered, arrival-order APIs (raw `recv_group`, the - arrivals handle from #5311, anything that yields in arrival order) get no - window and no drop. Not chosen: knowingly drop, with the floor at the first - arrival; require a declared start floor for retained windows. -- Give both consumers the window where they lack it (Rust's no-start startup - adopts its cursor from the first arrival today), then make JS discard groups - below the cursor. `consumer.outoforder.test.ts`'s case must keep passing - under the window: the head arriving after the live edge is still delivered. - Add a test mirroring Rust's: a reordered group below the settled floor is - never yielded and no extra `Gap` appears. +- ✅ Decided 2026-10-15 (maintainer), superseding the 2026-10-13 settlement + window: no window. Holding the first delivery for a max-delay span adds + startup latency to every ordered read without a declared start, and + [#5508](https://github.com/moq-dev/moq/pull/5508) (which implemented it) was + closed. Current behavior stays until the publisher reports the track's + bounds; then the ordered read takes its floor from them. +- Groups are served newest-first, so the head of a retained window can arrive + after the live edge; a cursor set from the first arrival would lose valid + startup media (`consumer.outoforder.test.ts` covers that order). With + bounds known up front, the floor is the oldest group the subscription + covers ([Subscribe ranges](/quest/m1/subscribe-ranges/README.md)) and the + live edge is the reported largest location + ([Live largest location](/quest/m1/largest.md)), so the read starts at the + right group without waiting. +- Once both land: set the ordered read's floor from those bounds in Rust and + JS, then make JS discard groups below the cursor. Add a test mirroring + Rust's: a reordered group below the floor is never yielded and no extra + `Gap` appears, and the out-of-order head case keeps passing. - Check JS readers that relied on backfill (watch's audio/video decoders, `Sync`) and adjust them; one that wants every group reads an arrival-order API instead. -Public API: no type change; the ordered read changes in Rust and JS (the first -delivery waits for the window when no start is declared, and JS drops -backfill). Wire: none. +Public API: no type change; the ordered read changes in Rust and JS (the +floor comes from reported bounds, and JS drops backfill). Wire: none beyond +the required quests. + +## Required + +- [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - the subscription reports the oldest group it covers +- [Live largest location](/quest/m1/largest.md) - the reader learns the live edge before the first group arrives diff --git a/quest/m1/test-flakes-2/README.md b/quest/m1/test-flakes-2/README.md index be6d3710e7d..d305a6c9863 100644 --- a/quest/m1/test-flakes-2/README.md +++ b/quest/m1/test-flakes-2/README.md @@ -33,6 +33,7 @@ Public API: none. Wire: none. ## Required - [TS decoded content](/quest/m1/test-flakes-2/ts-video-content.md) - CI's TS compliance decoded-content checks fail only on real loss, and the exporter names every frame it drops +- [TS resume late frames](/quest/m1/test-flakes-2/ts-resume-late-frames.md) - the same-instance TS resume test keeps its late frames, fixed in production or the fixture as the evidence says - [WebTransport close release](/quest/m1/test-flakes-2/interop-close-release.md) - a compatible published adapter contains the acknowledged-close fix - [Interop close code](/quest/m1/test-flakes-2/interop-close-code.md) - adopt that release so the browser reliably sees `unauthorized` for a refused session - [Handshake ACK](/quest/m1/test-flakes-2/handshake-ack.md) - the impaired cluster drill's connect survives a lost handshake ACK @@ -42,6 +43,7 @@ Public API: none. Wire: none. - [Export exit tests](/quest/m1/test-flakes-2/export-failure-exit.md) - moq-cli's failed-export and interrupted-publisher exit tests hold under load - [Session registration](/quest/m1/test-flakes-2/cli-drain-register.md) - a session the client sees established is one the CLI's drain waits for, so a SIGTERM right after the handshake still sends GOAWAY - [noq client close](/quest/m1/test-flakes-2/noq-client-close.md) - moq-tokio's client close test orders its close after the server's accept instead of racing it +- [BBR marking seed](/quest/m1/test-flakes-2/bbr-marking-seed.md) - the BBR marking test seeds its probe schedule, replays a failing seed, and its rare drop is fixed at the cause ## Related diff --git a/quest/m1/test-flakes-2/bbr-marking-seed.md b/quest/m1/test-flakes-2/bbr-marking-seed.md new file mode 100644 index 00000000000..1c89128016d --- /dev/null +++ b/quest/m1/test-flakes-2/bbr-marking-seed.md @@ -0,0 +1,31 @@ +# [S] BBR marking test replays its seed + +## Goal + +`bbr_marking_versus_dropping` (`rs/moq-quic/src/tests/mod.rs`) gives the same +verdict on every run of the same code. It seeds BBR's probe schedule, prints +the seed when it fails, and replays bit-exact from that seed. The packet +drop it can hit is explained and fixed at its cause. + +## Plan + +Found while landing [#5524](https://github.com/moq-dev/moq/pull/5524): on +unmodified `main`, a 20-run stress of this test failed once, at iteration +19, with one dropped packet on the marking run (`marking.dropped == 0`). +The test builds `Bbr3Config::default()`, so `probe_rng_seed` is unset and +`Bbr3::new` (`rs/moq-quic/src/congestion/bbr3/mod.rs`) draws the probe wait +from an unseeded RNG. + +- ✅ Decided 2026-10-15 (maintainer): a CI flake, so it lives here in m1. +- Seed the test through `probe_rng_seed`, sweep enough seeds to find the + failing schedules, and keep the seed in the failure message. +- Capture the probe schedule of a failing seed and decide whether the drop + is a controller bug (BBR fails to answer CE in time) or the bottleneck + model. Keep the acceptance thresholds and controller policy unchanged + while gathering evidence; change either only with the evidence in hand. +- Check the sibling BBR tests for the same unseeded RNG. + +## Related + +- [Seeded sim](/quest/m2/sim-seed.md) - the same replay rule for the moq-clock sim +- [BBR ACK cleanup](/quest/m2/bbr-ack-cleanup.md) - the change that surfaced it diff --git a/quest/m1/test-flakes-2/ts-resume-late-frames.md b/quest/m1/test-flakes-2/ts-resume-late-frames.md new file mode 100644 index 00000000000..a9fe5995df5 --- /dev/null +++ b/quest/m1/test-flakes-2/ts-resume-late-frames.md @@ -0,0 +1,32 @@ +# [M] TS same-instance resume keeps its late frames + +## Goal + +`the_same_instance_within_the_linger_continues` (`rs/moq-cli/tests/export.rs`) +gives the same verdict on every run of the same code. Its late-frame drops +are either fixed in production or shown to be fixture scheduling and fixed +in the fixture, whichever the evidence says. The strict assertion stays: +the resumed output must contain new media. + +## Plan + +Found while landing [#5545](https://github.com/moq-dev/moq/pull/5545), which +rebuilt the fixture around one importer across a real relay connection loss, +and seen again on [#5569](https://github.com/moq-dev/moq/pull/5569). In one +loaded run the exporter dropped late frames and the test failed; a later run +passed with no fixture change. + +- Reproduce under load first (loop the test beside a loaded `just check`), + keeping the exporter stderr #5545 now retains, and record which frames + were dropped and why. +- Decide the cause from that evidence. Production: the exporter or relay + drops frames a real returning publisher would also lose, so fix it there. + Fixture: the test feeds the capture tail faster or later than a real + publisher would, so pace it on the media clock or a mocked clock. +- No retries, wider timeouts, or a looser new-content assertion. +- Compare with [TS decoded content](/quest/m1/test-flakes-2/ts-video-content.md): + its first hypothesis (a zero `max_delay` live subscription) may cause both. + +## Related + +- [TS decoded content](/quest/m1/test-flakes-2/ts-video-content.md) - the CI TS compliance flake, possibly the same cause diff --git a/quest/m1/test-flakes-2/ts-video-content.md b/quest/m1/test-flakes-2/ts-video-content.md index 0f827e11379..2285fa89463 100644 --- a/quest/m1/test-flakes-2/ts-video-content.md +++ b/quest/m1/test-flakes-2/ts-video-content.md @@ -58,6 +58,20 @@ failed 3 of 4 runs, in two other ways: So in each case the checks saw real loss. The flake is what makes the exporter lose content, and the loss goes unreported. +2026-10-15: the same `video-content` failure blocked unrelated #5566 twice in +one run ([38725177110](https://github.com/moq-dev/moq/actions/runs/38725177110), +both attempts), and hit #5553 and #5148 again, so the rate is above the 4 in +41 estimate. In each of #5553's and #5566's `sub.log`, about 20 ms after the +video subscribe: `current group evicted ... group=1 +error=Hang(Moq(Stream(Old)))`, and `relay.log` and `pub.log` show +`max_delay: 0ns` on `0.avc3`. The publisher serves group 1, group 2's first +frame lands about 17 ms later, `poll_drifted` judges group 1 zero behind the +edge, and it resets group 1's stream `Old` before the raised budget reaches +it through the relay. That confirms hypothesis 1's first candidate (analysis +on #5565). Capture size does not separate the cases at a 1 s `--delay`: a +passing local run captured 25156468 bytes, like a failing CI run, so use the +log signature. + ### Hypotheses 1. Startup truncation (CI). The importer holds the catalog for its 2 s PCR @@ -115,6 +129,10 @@ pre-IDR content in group 0 (the importer opens a group only at a keyframe). Public API: none expected; the drop count may surface in the exporter's summary. Wire: none. +## Closes + +- [#5565](https://github.com/moq-dev/moq/issues/5565) - the TS compliance video-content flake + ## Related - [#5484](https://github.com/moq-dev/moq/pull/5484) - removes the WebSocket-to-QUIC upgrade behind local mode 1 From 12e9a6e84e561a2f1699a9530243a7ffb83aba5c Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 10:19:55 -0700 Subject: [PATCH 2/8] quest: address review on the 2026-10-15 follow-ups Point the auth quests at #5148's rewrite of request-token, scope request-update's lite path to request-auth-id, bound the unknown-ID wait, flag the lite renewal race, add concept docs, name #5527 in the CI entry, and stop treating SUBSCRIBE_OK's Group as read-backfill's floor. Co-Authored-By: Claude Opus 5.5 --- quest/m0/ci/README.md | 2 +- quest/m1/auth/request-auth-id.md | 16 +++++++++++++--- quest/m1/auth/request-update.md | 19 +++++++++++++++---- quest/m1/read-backfill.md | 24 ++++++++++++++++-------- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/quest/m0/ci/README.md b/quest/m0/ci/README.md index 5c268f88654..f827f81858f 100644 --- a/quest/m0/ci/README.md +++ b/quest/m0/ci/README.md @@ -57,4 +57,4 @@ Measure each change against the numbers above and record the result in its PR. - [Require platform gates](/quest/m0/ci/platform-queue.md) - activate the consolidated platform checks in the main and release rulesets after the workflow lands - [mbx on R2](/quest/m0/ci/mbx-r2.md) - Rust builds on every OS read an R2 cache that only `main` writes, escaping the 10 GB Actions cache - [Nix cache](/quest/m0/ci/nix-cache.md) - `nix.moq.dev` on R2 replaces Cachix for CI and releases -- [Nightly after FFI reuse](/quest/m0/ci/nightly-ffi-reuse.md) - condition: the first scheduled Nightly and `moq-ffi-v*` tag after #5549; measure them and verify the shared build +- [Nightly after FFI reuse](/quest/m0/ci/nightly-ffi-reuse.md) - condition: the first scheduled Nightly and `moq-ffi-v*` tag after #5549 and #5527; measure them and verify the shared build diff --git a/quest/m1/auth/request-auth-id.md b/quest/m1/auth/request-auth-id.md index e70700a03d7..ac2b711e773 100644 --- a/quest/m1/auth/request-auth-id.md +++ b/quest/m1/auth/request-auth-id.md @@ -16,6 +16,9 @@ Step 2 of the unified auth model settled on [#5148](https://github.com/moq-dev/moq/pull/5148) on 2026-10-15. Step 1 (#5148, [Request tokens](/quest/m1/auth/request-token.md)) makes a moq-transport request token an auth on the shared `requests()` queue. +#5148 is rewriting that quest and the auth README from its older `scoped()` +queue and `set_request_token` design to this unified model; read it there +until it lands. - ✅ Decided 2026-10-15 (maintainer): one kind of auth. Each request names at most one (`auth=id`); moq-transport's per-request token is only an encoding @@ -29,16 +32,23 @@ moq-transport request token an auth on the shared `requests()` queue. - ✅ Decided 2026-10-15 (maintainer): spec it in lite-07-wip now, before the cut, so it doesn't cost lite-08. - A request naming an ID that hasn't arrived yet waits for it while holding - its slot; one naming an ID that already ended is refused UNAUTHORIZED. + its slot; one naming an ID that already ended is refused UNAUTHORIZED. The + wait spends only the requester's own session budget (its stream and + subscription limits), and ends when the AUTH arrives, the requester cancels, + or the session closes; no timer. - Renewal on lite: add a new auth, SUBSCRIBE_UPDATE the request to it, drop - the old one. + the old one. AUTH and Subscribe streams are unordered, so dropping the old + auth must not race the update into an UNAUTHORIZED reset: settle what tells + the renewer the update applied before it drops the old auth, and spec it. - moq-transport: AUTH's Request ID becomes the auth id, and a new request parameter names it. The alias types map as: USE_VALUE registers a request-lifetime auth and names it; REGISTER registers a session-lifetime auth; USE_ALIAS names one; DELETE withdraws one. - Update `drafts/draft-lcurley-moq-lite.md` and `drafts/draft-lcurley-moq-auth.md`, Rust and `js/net`, and run - `just test interop --all`. + `just test interop --all`. Update `doc/concept/moq-lite.md`'s + Authorization section (scope is no longer only the session union) and + `doc/concept/standard.md`, which says a request's token grants nothing. Public API: none beyond step 1's `requests()` and `add()`; naming is automatic. Wire: lite-07-wip AUTH and five request messages gain fields. diff --git a/quest/m1/auth/request-update.md b/quest/m1/auth/request-update.md index 8f0be4f7220..7e259bbdabb 100644 --- a/quest/m1/auth/request-update.md +++ b/quest/m1/auth/request-update.md @@ -14,7 +14,10 @@ REQUEST_UPDATE with a fresh token before it expires. Step 3 of the unified auth model settled on [#5148](https://github.com/moq-dev/moq/pull/5148) on 2026-10-15, ranked right -after step 1 because it has a consumer now. +after step 1 because it has a consumer now. #5148 is rewriting +[Request tokens](/quest/m1/auth/request-token.md) and the auth README from +their older `scoped()` queue and `set_request_token` design to this unified +model; read it there until it lands. - ✅ Decided 2026-10-15 (maintainer): general request updates, not a per-request token setter. Each `Route`/`Subscription` carries an @@ -25,11 +28,15 @@ after step 1 because it has a consumer now. - PUBLISH_NAMESPACE renewal comes first. Per-SUBSCRIBE tokens follow the same shape but have no consumer yet; include them only if they fall out of the same code. -- On moq-transport the update carries the token as USE_VALUE; on lite it - names the auth registered through [Requests name their auth](/quest/m1/auth/request-auth-id.md) - once that lands. A refused update ends only that request. +- On moq-transport the update carries the token as USE_VALUE and needs only + step 1, so the PUBLISH_NAMESPACE consumer doesn't wait on the lite wire. On + lite it names the auth registered through + [Requests name their auth](/quest/m1/auth/request-auth-id.md), so the lite + path follows that quest. A refused update ends only that request. - Settle the type and field names with the maintainer before implementing; mirror them in `js/net` and the bindings. +- Describe request renewal in `doc/concept/moq-lite.md`'s Authorization + section. Public API: a new optional auth on routes and subscriptions in Rust and JS. Wire: none beyond existing REQUEST_UPDATE and SUBSCRIBE_UPDATE. @@ -37,3 +44,7 @@ Wire: none beyond existing REQUEST_UPDATE and SUBSCRIBE_UPDATE. ## Required - [Request tokens](/quest/m1/auth/request-token.md) - step 1: request tokens become auths on the shared acceptor queue + +## Related + +- [Requests name their auth](/quest/m1/auth/request-auth-id.md) - the lite path's prerequisite; moq-transport renewal doesn't wait on it diff --git a/quest/m1/read-backfill.md b/quest/m1/read-backfill.md index 367f34cabaf..eadca834b75 100644 --- a/quest/m1/read-backfill.md +++ b/quest/m1/read-backfill.md @@ -25,12 +25,20 @@ delivers a group that arrives below the cursor as a late backfill; Rust bounds; then the ordered read takes its floor from them. - Groups are served newest-first, so the head of a retained window can arrive after the live edge; a cursor set from the first arrival would lose valid - startup media (`consumer.outoforder.test.ts` covers that order). With - bounds known up front, the floor is the oldest group the subscription - covers ([Subscribe ranges](/quest/m1/subscribe-ranges/README.md)) and the - live edge is the reported largest location - ([Live largest location](/quest/m1/largest.md)), so the read starts at the - right group without waiting. + startup media (`consumer.outoforder.test.ts` covers that order). The floor + is instead the lowest group of the subscription's requested ranges + ([Subscribe ranges](/quest/m1/subscribe-ranges/README.md)): given for an + absolute start, and for a relative start resolved against the largest + location the publisher reports + ([Live largest location](/quest/m1/largest.md)). Every sequence in range + arrives or is named by SUBSCRIBE_DROP, so the read never waits on a group + that won't come. +- SUBSCRIBE_OK's `Group` is not the floor: the lite draft lets a group + between the requested start and it arrive later. +- Where group numbers are sparse (moq-transport), a relative start can't be + resolved from the largest location alone. Settle there whether the + publisher reports the resolved start or that read keeps today's + first-arrival cursor. - Once both land: set the ordered read's floor from those bounds in Rust and JS, then make JS discard groups below the cursor. Add a test mirroring Rust's: a reordered group below the floor is never yielded and no extra @@ -45,5 +53,5 @@ the required quests. ## Required -- [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - the subscription reports the oldest group it covers -- [Live largest location](/quest/m1/largest.md) - the reader learns the live edge before the first group arrives +- [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - the reader's requested ranges set the floor, and every sequence in them arrives or is dropped +- [Live largest location](/quest/m1/largest.md) - the reader learns the live edge before the first group arrives, resolving a relative start From 1149cddd156c8bd84b8d7f7c9dd88e41bf8ef898 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 10:31:57 -0700 Subject: [PATCH 3/8] quest: settle REGISTER, repeated tokens, and disjoint-range reads Co-Authored-By: Claude Opus 5.5 --- quest/m1/auth/request-auth-id.md | 7 ++++++- quest/m1/read-backfill.md | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/quest/m1/auth/request-auth-id.md b/quest/m1/auth/request-auth-id.md index ac2b711e773..d0b0ef78366 100644 --- a/quest/m1/auth/request-auth-id.md +++ b/quest/m1/auth/request-auth-id.md @@ -23,6 +23,9 @@ until it lands. - ✅ Decided 2026-10-15 (maintainer): one kind of auth. Each request names at most one (`auth=id`); moq-transport's per-request token is only an encoding of that model, never a separate API. +- Settled in the #5148 design: at most one token per moq-transport request. + Distinct tokens are refused NOT_SUPPORTED, and a repeated identical token is + refused MALFORMED_AUTH_TOKEN. Test both. - ✅ Decided 2026-10-15 (maintainer): an unnamed request is admitted by the union of session-lifetime auths only, so a request-lifetime token never widens tokenless requests. @@ -43,7 +46,9 @@ until it lands. - moq-transport: AUTH's Request ID becomes the auth id, and a new request parameter names it. The alias types map as: USE_VALUE registers a request-lifetime auth and names it; REGISTER registers a session-lifetime - auth; USE_ALIAS names one; DELETE withdraws one. + auth and names it for the carrying request, so that request ends when the + auth ends or narrows away from it; USE_ALIAS on a later request names it; + DELETE withdraws one. - Update `drafts/draft-lcurley-moq-lite.md` and `drafts/draft-lcurley-moq-auth.md`, Rust and `js/net`, and run `just test interop --all`. Update `doc/concept/moq-lite.md`'s diff --git a/quest/m1/read-backfill.md b/quest/m1/read-backfill.md index eadca834b75..976ec1eab59 100644 --- a/quest/m1/read-backfill.md +++ b/quest/m1/read-backfill.md @@ -39,10 +39,14 @@ delivers a group that arrives below the cursor as a late backfill; Rust resolved from the largest location alone. Settle there whether the publisher reports the resolved start or that read keeps today's first-arrival cursor. +- The cursor is range-aware, not just floored: with disjoint ranges it skips + straight across an unrequested gap (group 1, then 10 onward) instead of + waiting for the latency budget, since no SUBSCRIBE_DROP is owed there. - Once both land: set the ordered read's floor from those bounds in Rust and JS, then make JS discard groups below the cursor. Add a test mirroring Rust's: a reordered group below the floor is never yielded and no extra - `Gap` appears, and the out-of-order head case keeps passing. + `Gap` appears, and the out-of-order head case keeps passing. Add a + disjoint live-range regression for the skip. - Check JS readers that relied on backfill (watch's audio/video decoders, `Sync`) and adjust them; one that wants every group reads an arrival-order API instead. From a55cd1a96ab38dfdde96f56d1692f8ce5539e773 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 10:33:32 -0700 Subject: [PATCH 4/8] quest: take read-backfill's relative floor from the publisher's resolution Co-Authored-By: Claude Opus 5.5 --- quest/m1/read-backfill.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/quest/m1/read-backfill.md b/quest/m1/read-backfill.md index 976ec1eab59..125e87f0769 100644 --- a/quest/m1/read-backfill.md +++ b/quest/m1/read-backfill.md @@ -28,17 +28,18 @@ delivers a group that arrives below the cursor as a late backfill; Rust startup media (`consumer.outoforder.test.ts` covers that order). The floor is instead the lowest group of the subscription's requested ranges ([Subscribe ranges](/quest/m1/subscribe-ranges/README.md)): given for an - absolute start, and for a relative start resolved against the largest - location the publisher reports - ([Live largest location](/quest/m1/largest.md)). Every sequence in range - arrives or is named by SUBSCRIBE_DROP, so the read never waits on a group - that won't come. -- SUBSCRIBE_OK's `Group` is not the floor: the lite draft lets a group - between the requested start and it arrive later. -- Where group numbers are sparse (moq-transport), a relative start can't be - resolved from the largest location alone. Settle there whether the - publisher reports the resolved start or that read keeps today's - first-arrival cursor. + absolute start, and for a relative start the start the publisher resolved + for this request. Every sequence in range arrives or is named by + SUBSCRIBE_DROP, so the read never waits on a group that won't come. +- Don't infer a relative start from the reported largest location: a + frameless newest group counts when a relative end resolves but not in + `largest()`, sparse moq-transport groups break the arithmetic, and a later + coalesced largest isn't this request's resolution. SUBSCRIBE_OK's `Group` + isn't it either, since the lite draft lets a group between the requested + start and it arrive later. If the wire doesn't carry the resolved start, + add it to lite-07 through [Lite-07 ranges](/quest/m1/subscribe-ranges/lite.md); + settle the moq-transport equivalent or keep today's first-arrival cursor + there. - The cursor is range-aware, not just floored: with disjoint ranges it skips straight across an unrequested gap (group 1, then 10 onward) instead of waiting for the latency budget, since no SUBSCRIBE_DROP is owed there. @@ -46,16 +47,17 @@ delivers a group that arrives below the cursor as a late backfill; Rust JS, then make JS discard groups below the cursor. Add a test mirroring Rust's: a reordered group below the floor is never yielded and no extra `Gap` appears, and the out-of-order head case keeps passing. Add a - disjoint live-range regression for the skip. + disjoint live-range regression for the skip, and a relative start whose + newest group has no frames yet. - Check JS readers that relied on backfill (watch's audio/video decoders, `Sync`) and adjust them; one that wants every group reads an arrival-order API instead. Public API: no type change; the ordered read changes in Rust and JS (the -floor comes from reported bounds, and JS drops backfill). Wire: none beyond -the required quests. +floor comes from reported bounds, and JS drops backfill). Wire: possibly a +resolved start on lite-07 SUBSCRIBE_OK, through Lite-07 ranges. ## Required - [Subscribe ranges](/quest/m1/subscribe-ranges/README.md) - the reader's requested ranges set the floor, and every sequence in them arrives or is dropped -- [Live largest location](/quest/m1/largest.md) - the reader learns the live edge before the first group arrives, resolving a relative start +- [Live largest location](/quest/m1/largest.md) - the reader learns the live edge before the first group arrives From 1c98e17ded321b8affe13a6c8d318fb618b75cac Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 10:44:41 -0700 Subject: [PATCH 5/8] quest: reserve the maximum auth ID and benchmark auth fan-out Co-Authored-By: Claude Opus 5.5 --- quest/m1/auth/request-auth-id.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/quest/m1/auth/request-auth-id.md b/quest/m1/auth/request-auth-id.md index d0b0ef78366..c9d125c347a 100644 --- a/quest/m1/auth/request-auth-id.md +++ b/quest/m1/auth/request-auth-id.md @@ -49,6 +49,10 @@ until it lands. auth and names it for the carrying request, so that request ends when the auth ends or narrows away from it; USE_ALIAS on a later request names it; DELETE withdraws one. +- An AUTH with ID `u64::MAX` is refused, since no request can name it as + ID plus 1. Cover the boundary in the Rust and JS interop tests. +- Extend `rs/moq-net/benches/auth.rs` with a sweep over live auths and named + requests, measuring the end or narrowing of one auth. - Update `drafts/draft-lcurley-moq-lite.md` and `drafts/draft-lcurley-moq-auth.md`, Rust and `js/net`, and run `just test interop --all`. Update `doc/concept/moq-lite.md`'s From e735f78fe302af39e7bb4828bc2680ca98070740 Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 10:56:21 -0700 Subject: [PATCH 6/8] quest: document moq-transport request renewal in standard.md Co-Authored-By: Claude Opus 5.5 --- quest/m1/auth/request-update.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quest/m1/auth/request-update.md b/quest/m1/auth/request-update.md index 7e259bbdabb..1ea78fd18fe 100644 --- a/quest/m1/auth/request-update.md +++ b/quest/m1/auth/request-update.md @@ -36,7 +36,8 @@ model; read it there until it lands. - Settle the type and field names with the maintainer before implementing; mirror them in `js/net` and the bindings. - Describe request renewal in `doc/concept/moq-lite.md`'s Authorization - section. + section, and in `doc/concept/standard.md`, which says a request's token + grants nothing; the moq-transport path can land before the lite one. Public API: a new optional auth on routes and subscriptions in Rust and JS. Wire: none beyond existing REQUEST_UPDATE and SUBSCRIBE_UPDATE. From 00c16b49dda5f26e02f0f607e87b618c7e1563ed Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 11:03:12 -0700 Subject: [PATCH 7/8] quest: renew lite routes through ANNOUNCE_UPDATE Co-Authored-By: Claude Opus 5.5 --- quest/m1/auth/request-auth-id.md | 8 ++++---- quest/m1/auth/request-update.md | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/quest/m1/auth/request-auth-id.md b/quest/m1/auth/request-auth-id.md index c9d125c347a..5f888c48511 100644 --- a/quest/m1/auth/request-auth-id.md +++ b/quest/m1/auth/request-auth-id.md @@ -4,8 +4,8 @@ In `moq-lite-07-wip`, AUTH carries an `ID (i)` the opener chooses (increasing, never reused), and SUBSCRIBE, SUBSCRIBE_UPDATE, FETCH, -ANNOUNCE_REQUEST and ANNOUNCE_START carry `Auth (i)`: 0 for unnamed, -otherwise the ID plus 1. A named request is admitted only by that auth's +ANNOUNCE_REQUEST, ANNOUNCE_START and ANNOUNCE_UPDATE carry `Auth (i)`: 0 for +unnamed, otherwise the ID plus 1. A named request is admitted only by that auth's grant and ends when the auth ends or narrows away from it. The presenter names auths automatically. The `draft-lcurley-moq-auth` extension mirrors it for moq-transport, additively. @@ -39,7 +39,7 @@ until it lands. wait spends only the requester's own session budget (its stream and subscription limits), and ends when the AUTH arrives, the requester cancels, or the session closes; no timer. -- Renewal on lite: add a new auth, SUBSCRIBE_UPDATE the request to it, drop +- Renewal on lite: add a new auth, SUBSCRIBE_UPDATE (ANNOUNCE_UPDATE for a route) the request to it, drop the old one. AUTH and Subscribe streams are unordered, so dropping the old auth must not race the update into an UNAUTHORIZED reset: settle what tells the renewer the update applied before it drops the old auth, and spec it. @@ -60,7 +60,7 @@ until it lands. `doc/concept/standard.md`, which says a request's token grants nothing. Public API: none beyond step 1's `requests()` and `add()`; naming is -automatic. Wire: lite-07-wip AUTH and five request messages gain fields. +automatic. Wire: lite-07-wip AUTH and six request messages gain fields. ## Required diff --git a/quest/m1/auth/request-update.md b/quest/m1/auth/request-update.md index 1ea78fd18fe..2caa1897dff 100644 --- a/quest/m1/auth/request-update.md +++ b/quest/m1/auth/request-update.md @@ -5,8 +5,8 @@ A publisher or subscriber renews the credential behind a live request without reopening it. A `Route` or `Subscription` holds an optional `moq_net::Auth`; when the application swaps it, the session sends -REQUEST_UPDATE (moq-transport) or SUBSCRIBE_UPDATE (lite) naming the new -auth, and the request stays up. The first consumer is a client that keeps a +REQUEST_UPDATE (moq-transport) or SUBSCRIBE_UPDATE / ANNOUNCE_UPDATE (lite) +naming the new auth, and the request stays up. The first consumer is a client that keeps a PUBLISH_NAMESPACE alive past its credential's expiry by sending REQUEST_UPDATE with a fresh token before it expires. @@ -40,7 +40,8 @@ model; read it there until it lands. grants nothing; the moq-transport path can land before the lite one. Public API: a new optional auth on routes and subscriptions in Rust and JS. -Wire: none beyond existing REQUEST_UPDATE and SUBSCRIBE_UPDATE. +Wire: none beyond existing REQUEST_UPDATE, SUBSCRIBE_UPDATE and ANNOUNCE_UPDATE, +with lite's auth field from Requests name their auth. ## Required From 65beac99a5738d35780e3749f2c6fa951fd50fed Mon Sep 17 00:00:00 2001 From: Luke Curley Date: Thu, 15 Oct 2026 11:03:55 -0700 Subject: [PATCH 8/8] quest: name the lite renewal message per request kind Co-Authored-By: Claude Opus 5.5 --- quest/m1/auth/request-update.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quest/m1/auth/request-update.md b/quest/m1/auth/request-update.md index 2caa1897dff..71c06884442 100644 --- a/quest/m1/auth/request-update.md +++ b/quest/m1/auth/request-update.md @@ -5,8 +5,9 @@ A publisher or subscriber renews the credential behind a live request without reopening it. A `Route` or `Subscription` holds an optional `moq_net::Auth`; when the application swaps it, the session sends -REQUEST_UPDATE (moq-transport) or SUBSCRIBE_UPDATE / ANNOUNCE_UPDATE (lite) -naming the new auth, and the request stays up. The first consumer is a client that keeps a +REQUEST_UPDATE (moq-transport) naming the new auth; on lite a `Route` renews +via ANNOUNCE_UPDATE and a `Subscription` via SUBSCRIBE_UPDATE. The request +stays up. The first consumer is a client that keeps a PUBLISH_NAMESPACE alive past its credential's expiry by sending REQUEST_UPDATE with a fresh token before it expires.