Skip to content
Merged
1 change: 1 addition & 0 deletions quest/m0/ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 and #5527; measure them and verify the shared build
29 changes: 29 additions & 0 deletions quest/m0/ci/nightly-ffi-reuse.md
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 3 additions & 0 deletions quest/m1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -161,10 +162,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

Expand Down
2 changes: 2 additions & 0 deletions quest/m1/auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 67 additions & 0 deletions quest/m1/auth/request-auth-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# [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, 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.

## 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.
Comment thread
kixelated marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
#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
of that model, never a separate API.
Comment thread
kixelated marked this conversation as resolved.
- 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.
- ✅ 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. 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 (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.
- 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 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
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 six request messages gain fields.

## Required

- [Request tokens](/quest/m1/auth/request-token.md) - step 1: request tokens become auths on the shared acceptor queue
53 changes: 53 additions & 0 deletions quest/m1/auth/request-update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# [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) 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.

## 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. #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
`Option<moq_net::Auth>` (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 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, 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, SUBSCRIBE_UPDATE and ANNOUNCE_UPDATE,
with lite's auth field from Requests name their auth.

## Required

- [Request tokens](/quest/m1/auth/request-token.md) - step 1: request tokens become auths on the shared acceptor queue
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## 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
21 changes: 21 additions & 0 deletions quest/m1/catalog-consumer-opaque.md
Original file line number Diff line number Diff line change
@@ -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<hang::Consumer>` 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.
33 changes: 33 additions & 0 deletions quest/m1/cpp-ffi-reuse.md
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions quest/m1/kt-destroyed-arg.md
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading