chore: merge main into dev - #2951
Merged
Merged
Conversation
Co-authored-by: GPT-5 <noreply@openai.com>
…sampler (M0) (#2877) Co-authored-by: Claude <noreply@anthropic.com>
…ps (#2867) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…d0641fb99eee32 to 258712b0b7b1ddf8bddc9fc3b0faca682b2736c3 in the github-actions group (#2887) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
## Summary - Encode draft 17+ JS message parameters using each parameter's defined value type instead of the legacy key-parity framing. - Preserve draft 14 through draft 16 behavior and add matching JS/Rust wire vectors for raw uint8 and Location values. - Preserve exact Location identifiers without JavaScript `Number` rounding, including full `uint64` values in drafts 17+. - Root cause: the JS codec continued inferring value framing from parameter ID parity after draft 17 introduced definition-specific value encodings. Its legacy Location cache also used number-only QUIC varint helpers despite exposing bigint identifiers. Closes #2864. ## Public API changes - Add `Varint.decodeBigInt` for exact QUIC varint decoding. - Allow `Varint.encode` to accept `bigint` as well as `number`. - `Parameters.largest` now preserves full `uint64` identifiers when using drafts 17+. - Bump `@moq/net` from 0.3.0 to 0.3.1. ## Test plan - [x] Original head: `just check` - [x] Original head: `bun test js/net/src/ietf/ietf.test.ts` (88 passed) - [x] Original head: `just rs test -p moq-net test_param_` (13 passed) - [x] Intermediate head `859a765`: GitHub Actions `Check` and `Test` - [x] Latest head `4890cbd`: GitHub Actions `Check` and `Test` - [ ] Draft 17 JS-to-Rust browser smoke test. Playwright Chromium crashed with SIGTRAP before loading the publisher, so no protocol session formed. ## Cross-package sync - Updated both `js/net` and `rs/moq-net` golden vectors. - No draft update is needed because this corrects the implementations to the existing draft 17 framing. - No other language package exposes the JS `Varint` API. Co-authored-by: GPT-5 <noreply@openai.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: OpenAI Codex <noreply@openai.com>
Co-authored-by: GPT-5 <noreply@openai.com>
Co-authored-by: GPT-5 <noreply@openai.com>
Co-authored-by: OpenAI Codex <noreply@openai.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…le (#2828) Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Luke Curley <kixelated@gmail.com> Co-authored-by: GPT-5 <noreply@openai.com>
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Luke Curley <kixelated@gmail.com> Co-authored-by: OpenAI Codex <noreply@openai.com>
Two things kept moq-mux off wasm32, and the first is a latent bug on every target. The av1/h264/h265 splitters hold a `tokio::time::Instant`, but moq-mux declares tokio with only the `macros` feature. That compiles today purely because another workspace member enables tokio's `time`, and feature unification hands it over. Build moq-mux in a resolver context that doesn't, and it fails to find `tokio::time` at all. Swap it for `web_async::time::Instant`, which natively re-exports the very same tokio type (so nothing changes on native) and maps to wasmtimer on wasm. This is the migration moq-net already did. The catalog `Stream` trait then required `Send + 'static`. moq-net's handles are `Rc`-backed on wasm, so nothing there can satisfy it, and the bound exists to allow moving exporters between native threads rather than to express anything about the type. Add `kio::MaybeSend`, which is `Send` natively and vanishes on wasm, and bound `Stream` on that instead. Natively `MaybeSend` is implied by `Send` and implies it back, so every existing caller and impl is unaffected. kio is the right home rather than a private copy: moq-net already keeps its own `MaybeSend`-shaped helpers in `util.rs`, moq-mux is the second crate to need the bound, and moq-ffi is the third once it grows a wasm target. Both crates already depend on kio, and it carries no runtime. Extend `just rs wasm` to cover moq-mux so this cannot regress silently, since a host-target build will never notice. moq-mux has to share moq-wasm's cargo invocation to inherit `getrandom`'s `wasm_js` backend, and the recipe explains why splitting them fails. Verified: `just check` clean (workspace clippy plus the extended wasm gate), and 582 tests pass across kio and moq-mux. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
#2905) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude <noreply@anthropic.com>
…2910) Co-authored-by: Claude <noreply@anthropic.com>
… update (#2908) Co-authored-by: Claude <noreply@anthropic.com>
…ference (#2906) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…l clamps (#2940) Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Write down a policy the iroh page was silent on, and correct two things it got wrong. P2P is a local-network feature; across the internet, run a MoQ relay. The page presented hole punching and the iroh relay as one feature, but they are not the same trade. A direct path between two peers costs nothing and is the point. The relay forwards opaque UDP through an n0 server, which is WebRTC's TURN with none of what a MoQ relay buys for that same hop: it cannot cache a group or serve a second viewer from the first one's fetch, and it adds whatever latency its location costs. Discovery is not local either. Both n0 presets install a PkarrPublisher, a PkarrResolver, and (outside browsers) a DnsAddressLookup against n0's iroh.link, and N0DisableRelay is N0 plus relay_mode(Disabled) and nothing else. So enabling iroh publishes this endpoint's addressing record to a third party whatever the relay setting; disabling the relay only changes what the record holds. Resolution is the half a caller can avoid, via Client::with_iroh_addrs; publishing has no equivalent escape hatch. Disabling the relay costs more than the fallback. A relay is the rendezvous both sides reach first, so it is also how they coordinate the punch, and its probes are how an endpoint learns the public address it advertises. Turning it off leaves direct addresses only. That is still right for two peers on one link, who need none of the three, but it is unsafe as a blanket recommendation: on a cloud VM behind 1:1 NAT it leaves a relay advertising a private address nobody can route to. The relay config sample therefore stays at the default with the trade written out. Also: iroh:// and https:// are separate MoQ connections to a relay, with no automatic fallback between them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Semantic conflicts resolved beyond the textual ones: - js/net origin.ts: dev put the broadcast routing table there (#2705), main moved the origin *id* module there from lite/ (#2910). The table keeps origin.ts; the id module is now the internal js/net/src/hop.ts, shared by both wire protocols as main intended. - js/watch broadcast.ts: dev rejects a whole catalog when a rendition's broadcast reference escapes the root (#2630); main hides renditions whose broadcast is not announced (#2918). Both kept. The announcement gate moved into #relativeTarget so playback and rendition selection cannot disagree about what is reachable, and filterCatalog now covers text renditions. - js/net ietf publisher: main's options-object constructor plus dev's origin-backed broadcasts and main's cluster advert. - moq-ffi session: main's wasm32 browser client alongside dev's reconnecting moq_tokio::Connection, with Inner::Connection, MoqBackoff, and the moq_tokio::Status conversion gated to native. - moq-net ietf subscriber: main's Arrival parameter plus dev's GOAWAY drain cost. web-transport-wasm 0.6 implements the poll traits moq-net requires (moq-dev/web-transport#369), so the hand-written adapters in moq-wasm and moq-ffi are gone; both files are now just the dial. This is what unblocks moq-ffi's wasm32 build (#2911) under dev's poll-only transport (#2736). Two of main's additions were written against APIs dev had already changed, and merged cleanly because neither side touched the other's lines: - test/wasm harness published through Established.publish, removed by #2705. It now publishes into an Origin and passes publish: origin.consume(). - test/wasm, rs/justfile, moq-bench's hd.toml, and the new iroh doc invoked --server-bind / --server-version / --client-connect, which #2915 refuses. moq_net::model::resume::consecutive_updates_wake asserted an absolute wake count. main's kio Park now reuses a still-registered waiter (#2905), so applying a subscription change notifies a list the poll is parked on and self-wakes once. That costs a redundant poll and nothing else, while a lost wakeup parks the task forever, so the test measures the delta instead. moq-mux's tdt_round_trips_as_latest_value fails here and on dev alike; it is not a merge regression. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017wFQ5wqKbvWwET3G5MJXY9
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…-dev-b9f056 # Conflicts: # rs/moq-ffi/Cargo.toml # rs/moq-ffi/src/ffi.rs
…o-dev-b9f056 # Conflicts: # doc/concept/layer/iroh.md
They cross the ABI as `uint32_t` so no signature reaches them, which is what the ENUMS allowlist in build.rs exists to compensate for. `moq_video_format` and `moq_container_format` were never added to it, so moq.h declared neither and a C caller of `moq_publish_video` or `moq_publish_container` had to hardcode the discriminants. cpp/obs did not, hence the build failure. Nothing caught it because PR CI never compiled a C consumer until obs.yml arrived from main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017wFQ5wqKbvWwET3G5MJXY9
#2927 split `publish_media_stream` into `publish_video_stream` and `publish_container_stream`, and updated `py/moq-rs`, but not the smoke client that calls it. Every `python -> *` case failed on the publisher with `'BroadcastProducer' object has no attribute 'publish_media_stream'`. smoke.yml only runs on a PR touching test/smoke, test/ts, test/justfile, package.json, or itself, so #2927 never triggered it and the break has been sitting on dev waiting for the nightly. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017wFQ5wqKbvWwET3G5MJXY9
The harness subscribes with the default `Latency::REAL_TIME` budget, which drops a group the edge has moved past rather than delivering it late. #2890 made that enforcement real, so the publisher writing a group every 50ms can now retire one the reader is still on and the read fails with `old`. Requiring `previous + 1` and treating `old` as a case failure asserts a guarantee real-time delivery does not make. Skips are accepted and bounded by an attempt cap instead, so a subscription that never delivers a whole group still fails; what a delivered group has to be, in order and byte-exact, is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017wFQ5wqKbvWwET3G5MJXY9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges
mainintodev. 29 files conflicted textually; several more conflicted semantically and merged clean. Both branches moved during the work, so this also carries a second round of integration against the newer tips.Summary
Textual conflicts worth review
js/net/src/origin.tswas a genuine collision: dev put the broadcast routing table there (feat(js/net)!: route publish and consume through Origins, share one connection per relay #2705), main moved the origin id module there fromlite/(feat(js/net): implement the MoQ Cluster extension for moq-transport #2910). The table keepsorigin.ts; the id module became the internaljs/net/src/hop.ts, shared by both wire protocols as main intended. Nothing new is exported from the package entrypoint.js/watch/src/broadcast.tshad opposed intents: dev rejects a whole catalog when a rendition'sbroadcastreference escapes the root (fix!: reject a catalog whose broadcast reference escapes the root #2630); main hides individual renditions whose broadcast is not announced (fix(js/watch): hide renditions whose broadcast isn't announced #2918). Both are kept. The announcement gate moved into#relativeTargetso playback and rendition selection cannot disagree about what is reachable, andfilterCatalognow coverstextrenditions, which dev added.rs/moq-ffi/src/ffi.rs: dev's fix(ffi): release the state when a handle is cancelled #2935 rewroteTask<T>to holdOption<T>and release the state on cancel; main's feat(moq-ffi): compile for wasm32 #2911 had split the same type across native and wasm32. Resolved as dev's semantics under main's layering:T: kio::MaybeSendrather thanSend, the tworunbodies sharing dev's newdrive, andcancel's state-drop routed through thespawnhelper so it works on both targets.is_cancelledis native-only, since only the QUIC server distinguishes cancelled from busy.rs/moq-ffi/src/session.rs: main's wasm32 browser client alongside dev's reconnectingmoq_tokio::Connection.Inner::Connection,MoqBackoff, and themoq_tokio::Statusconversion are gated to native.js/net/src/ietf/publisher.ts: main's options-object constructor, plus dev's origin-backed broadcasts and main's cluster advert.rs/moq-net/src/ietf/subscriber.rs: main'sArrivalparameter plus dev's GOAWAY drain cost.Silent semantic conflicts (merged clean because neither side touched the other's lines)
test/wasm/'s harness published throughEstablished.publish, which feat(js/net)!: route publish and consume through Origins, share one connection per relay #2705 removed. It now publishes into anOriginand passespublish: origin.consume().test/wasm/,rs/justfile,rs/moq-bench/config/hd.toml, and main's new iroh page invoked--server-bind/--server-version/--client-connect, which fix(cli)!: refuse the renamed flags with a migration instead of ignoring them #2915 refuses outright. Reconciled against--help.timeline.rsneeded thearc_with_non_send_syncallow the rest ofmoq-muxalready carries, now that main compiles the crate for wasm32.doc/concept/layer/iroh.mdand the VitePress sidebar pointed at/lib/rs/crate/moq-native, dead since dev's rename. (The sidebar link was already dead ondev; VitePress only validates in-content links.)rs/moq-native/src/bind.rs, which rename detection correctly routed intors/moq-tokio/src/bind.rs.wasm32
web-transport-wasm0.6 implements the poll traitsmoq-netrequires (moq-dev/web-transport#369), so the hand-written adapters inmoq-wasmandmoq-ffiare deleted; both files are now just the dial (794 lines to 38, and 163 to 25). That is what lets main'smoq-ffiwasm32 build (#2911) coexist with dev's poll-only transport (#2736). Pin bumped to"0.6";web-transport-quinnpicks up 0.12.1 under the existing"0.12"pin andweb-transport-quicheis unchanged.Three pre-existing
devbugs, each in its own commitAll three were invisible on
devbecause the CI that catches them is path-filtered, and this merge touches the paths.cpp/obsdid not compile againstmoq.h. The codec enums cross the ABI asuint32_tso that an unknown discriminant from C is an error rather than UB, which leaves no signature reaching them; theENUMSallowlist inrs/libmoq/build.rsis what forces cbindgen to emit them anyway.moq_video_formatandmoq_container_formatwere never added to it, so the header declared neither and every C caller ofmoq_publish_video/moq_publish_containerhad to hardcode the discriminants. Nothing compiled a C consumer oflibmoqin PR CI untilobs.yml(feat(obs): compile the plugin in CI, and type-check it without obs-deps #2867) arrived from main.python -> *smoke case failed. refactor(bindings)!: split media publishing by kind #2927 splitpublish_media_streamintopublish_video_stream/publish_container_streamand updatedpy/moq-rs, but not the smoke client calling it, so the publisher died with'BroadcastProducer' object has no attribute 'publish_media_stream'.smoke.ymlruns on a PR only when it touchestest/smoke,test/ts,test/justfile,package.json, or itself; refactor(bindings)!: split media publishing by kind #2927 touched none of those.ietf: reads a published broadcastwithold. It subscribes with the defaultLatency::REAL_TIMEbudget, which drops a group the live edge has moved past rather than delivering it late. feat(moq-net): enforce the subscriber latency budget #2890 made that enforcement real, so a publisher writing every 50ms can retire a group the reader is still on. Demanding consecutive sequences and treatingoldas a failure asserted a guarantee real-time delivery does not make. Skips are now accepted and bounded by an attempt cap, so a subscription that never delivers a whole group still fails; order and byte-exactness of a delivered group are unchanged. (Contrast test(mux): read the SI history with a latency budget that keeps it #2948, which hit the same budget in a Rust test that wanted history, where a wider budget was the right answer.)One test changed deliberately
moq_net::model::resume::consecutive_updates_wakeasserted an absolute wake count of 2 and saw 3. The extra wake happens during the poll, not at the update: main'skio::Parknow reuses a still-registered waiter (#2905), so applying the subscription change notifies a list the poll is itself parked on and self-wakes once. That costs a redundant poll and nothing else, while a lost wakeup parks the task forever, so the assertion measures the delta now. Reason is recorded in the test.Public API changes
moq.hgains themoq_video_formatandmoq_container_formatdeclarations described above. That is additive: the discriminants are unchanged, so an existing caller that hardcoded them keeps working.Otherwise no new or changed public surface beyond what each parent already shipped. Internal only:
js/net/src/hop.tsis a new internal module (not re-exported from the entrypoint);moq_wasm::transport::Sessionis now a re-export ofweb_transport_wasm::Sessionrather than a local newtype (moq-wasmispublish = false);moq_ffi::transport's newtypes are gone in favour of the upstream types.Wire behavior changes
None. No encoding, message, or framing changed in either direction, so no
drafts/update applies.Testing
just rs check,just rs wasm-- passcargo nextest run --workspace-- 3295/3295 (dev's test(mux): read the SI history with a latency budget that keeps it #2948 fixed themoq-muxTS clock test that was failing ondevbefore this merge)bun run --filter='*' check(type-check + docs build) -- passjust obs ci-- all 11 targets build and link./smoke/smoke.sh --publishers python --subscribers rust,python-- passruff+pyrighton the changed Python -- cleanjust test wasm-- 9/9 browser cases across all three protocol flavours, which is the real check on the transport swap: compiling says nothing about whether the bindings still open a sessionbun biome check-- 3 errors, all pre-existing and identical on a pristinemaincheckout (cpp/obs/{CMakePresets,buildspec}.jsonformatting,doc/.vitepress/theme/index.jsimport order). Left alone rather than mixed into a merge.🤖 Generated with Claude Code
https://claude.ai/code/session_017wFQ5wqKbvWwET3G5MJXY9
(Written by Claude Opus 5 (1M context))