Skip to content

refactor!: the final API audit narrows surfaces added since the audit - #8088

Merged
kixelated merged 35 commits into
mainfrom
quest/m1/api-audit/README
Nov 8, 2026
Merged

kixelated merged 35 commits into
mainfrom
quest/m1/api-audit/README

Conversation

@kixelated

@kixelated kixelated commented Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

The final pass of the API audit epic (quest/m1/api-audit/README.md), which finishes it: its last child landed in moq-dev/moq#8121, so this PR deletes the epic. I diffed main's public API against release's and the audit base, then made private, removed, or renamed each surface added since the audit that breaks the epic's rules. The maintainer answered the five open decisions on 2026-11-06; they are recorded below.

Maintainer decisions (2026-11-06)

  1. Kept public: the m3 realtime-uplink bake-off surfaces, moq_congestion::{gcc, scream}, moq-tokio's quic::Controller hook (Config.controller, Resolved.controller, has_controller, moq_tokio::congestion), and moq-quic's transport::Config.receive_timestamps.

  2. Kept public: all of moq_crypto::sign, signing included, for the m3 plans. Not chosen: removing signing.

  3. The quic::web errors become one typed web::Error per crate in moq-tokio and moq-uring, not in this PR. moq-dev/moq#8167 landed it.

  4. Kept public: moq-stats' aggregate::{Consumer::group, GroupConsumer} and group_track.

  5. Made private in this PR, at a fixed value: moq_uring::Config.passes, the per-turn task pass budget. It is fixed at 4, the default since moq-dev/moq#7530. That PR chose four from a joint sweep of 1, 2, 4, and 8 passes, where eight found no additional chat work. Its 1-versus-4 ablation on worker_turns (release profile, repeated A/A) showed one pass splitting each wake chain across turns:

    • one connection, 64 B: 8.0 to 2.0 turns per round
    • one connection, 256 KiB: 14.4 to 4.3 turns and 8.4 to 4.3 enters
    • 16 connections, 256 KiB: 140-150 to 73-75 turns and 90-96 to 73-75 enters

    I found no nightly results that contradict it. The field is now pub(crate) so the in-crate 1/2/4/8 hand-off test still sweeps it, and worker_turns drops its passes axis.

Also dropped: the follow-up to make moq-relay's Web builder private. The maintainer decided on moq-dev/moq#8082 to keep one public Web::with_listen setter.

How the audit ran

  • I extracted the public API at release (a005c7a), the audit base (d44813c), and main (f2f9bc9):
    • Rust: rustdoc JSON with --all-features, walked from each crate root. Unpublished and demo crates are excluded, and crate renames are normalized (moq-clock to kio::clock, moq-e2ee to moq_crypto::e2ee, moq_quic::congestion to moq-congestion).
    • JS: the TypeScript checker over each published package's exports.
    • C: the header.
  • That gave 5,167 Rust paths and 801 JS keys present on main but absent from both release and the base. Seven read-only reviewers (JS core, JS media, JS player, Rust net, transport, media, bindings) judged each cluster against the epic's rules. They searched for consumers in this repository, moq.pro, moq.dev, and studio at origin/main, plus m1/m2 quests.
  • Five fixers then verified each finding before changing it, refuted the wrong ones (listed below), and committed per area. Their branches are merged here.
  • By raw path count the public API did not shrink before this pass. Rust had 26,093 paths on main against 20,065 on release, and JS had 4,114 keys against 3,936. Most of the growth is new crates and features (moq-quic's public API, qmux, moq-congestion, moq-crypto, @moq/ui), not duplicated surface.

Changes

JS

  • Unexported or @internal: Report.PRIORITY, Report.Writer.wanted, ConsumerProps.maxBuffer, DEFAULT_MAX_BUFFER, LatencyEntrySchema, createFullscreen, Fullscreen, GraphOptions.{max, samples}, FanoutProps, Fanout's constructor and close, Broadcast.STATS_TRACK, Datagram.Request, IMPLEMENTATION, and Path.MAX_BYTES.
  • Removed Session.streamCredit and StreamCredit from @moq/qmux. The maintainer reversed the transport credit API in moq-dev/moq#7894.
  • Renamed for one name per meaning and mirrored names:
    • Catalog.Audio and Catalog.Video export only ranked.
    • Group.Frame{Info, Producer, Consumer} became Frame.{Info, Producer, Consumer}.
    • Moq.socket became Moq.Socket.
  • Element properties nothing reassigns are now readonly: <moq-watch> connection, emitter, text, and textRenderer, and <moq-publish> connection, broadcast, video, and audio.

Rust net, auth, crypto

  • moq_net::Datagram lost its root re-export, so the type now has one path.
  • Error::{EmptySegment, InvalidEscape} became Error::Path(path::Error). Error codes are unchanged, and moq-webrtc maps a bad path to 400 without matching on moq-net errors.
  • Handle::add returns only the Credential.
  • Private or removed: Tokens::{dial, connected, changed}, auth::Dial, auth::Request::{kind, path}, RequestKind, Answer, Session::{poll_idle, idle}, Version::has_setup_token, track::Producer::abort_unwanted, datagram::Info::produce, datagram is_clone, socket::Dialing::session, and stats::Activity::WINDOW.
  • Removed moq-auth's peer grants (PeerGrant, sign_peer, verify_peer, to_peer_set, and two error variants), and deleted the quest that planned it, quest/m4/auth-drop-peer-grant.md.

Transport and relay

  • moq-quic gives each item one path: send and recv at the root; the stats types, Streams, and Datagrams under connection::.
  • Removed the dead connection::Event and Connection::poll.
  • Removed transport::Config.max_outgoing_bytes_per_second, which overlapped Limits.send_rate, along with the Init.max_pacing_rate it fed.
  • Private:
    • moq-quic: poll_acked and poll_handshake_confirmed.
    • moq-sock: demux::{classify, Class, Queue::SHARE}.
    • moq-relay: the uring module.
    • moq-tokio: listen::Forward::configured.
    • moq-congestion: Bbr3 and Cubic. Its root re-exports are removed.
  • moq-uring's Config.passes is private, fixed at four (maintainer decision 5).
  • --listen-implementation and --connect-backoff-healthy are code-only now, matching connect.implementation.
  • tcp::Listener::accept and websocket::Listener::accept return Result instead of the Option<Result> that their docs said was kept only for compatibility.

Media and gateways

  • Renamed:
    • hang: Catalog::media() is now without_ext(), since Catalog.media is the media section.
    • moq-rtmp: with_max_delay is now with_delay.
    • moq-srt: with_max_delay is now with_latency, its release name.
    • moq-mux and moq-hls: with_passthrough / passthrough are now with_cmaf / cmaf, matching the CLI's --cmaf.
  • Private:
    • moq-mux: Consumer::set_max_buffer, DEFAULT_MAX_BUFFER, and fmp4::MAX_*.
    • moq-stats: consume::{Consumer::group, GroupConsumer}.
    • moq-hls: Broadcaster::is_clone and Rendition::{interval, segment_length}.
  • Removed the hidden moq auth serve --mtls-peer / --mtls-upstream refusals. Those spellings never shipped in release.

Bindings

  • moq-ffi renames:
    • MoqFill::{start_group, start_frame, end_group} became start() -> MoqPosition and end(), mirroring track::Fill.
    • MoqTrackRequest::abort and MoqDatagramRequest::abort became reject.
  • Go: GrantConsumer.Close and BandwidthConsumer.Close became Cancel.
  • Python and Go: JSON window events are named WindowEvent{Push, Pop, Skip}, and Go's TimedWindowEvent is now WindowTimed.
  • Python and Swift wrap VideoInit, as Go does, so lower is set once.

Quests

  • quest/m3/release/npm-first-publish.md now includes @moq/ui, which npm view still returns 404 for and which @moq/watch and @moq/publish depend on.
  • Deleted quest/m1/api-audit/README.md with quest rm, along with its Required entries in m1 and m2 and its prose mentions in quest/m1/README.md, quest/m2/recut-held.md, and quest/m2/group-drops.md. Its last child, join.md, landed in moq-dev/moq#8121.

Surfaces main added since the audit's head

Merging origin/main (142b03b) brought in new public API since the reviewed head 9aa0ce3. Each item was checked against the epic's rules. None breaks them, so this merge changes none:

  • @moq/net Track.Consumer.{floor, start, nextInRange} (moq-dev/moq#8071): @moq/hang's container reader calls all three, and each mirrors a Rust Subscriber method (floor, poll_start, next_in_range). Rust Subscriber::poll_start is new too; moq-mux's container consumer calls it.
  • @moq/qmux Config.handshakeTimeout (moq-dev/moq#8054): mirrors Rust qmux's Config.handshake_timeout, next to idleTimeout. qmux's ws connect/accept became async in the same PR, with no new surface.
  • moq-tokio Server::accept_with and Client::connect_with (moq-dev/moq#8085): the WebRTC transport calls them, and quest/m2/webrtc-rename.md moves it to moq-webrtc. The session-policy quest checked them against this epic's consumer rule. Incoming is pub(crate).
  • moq-audio playback::Observation.comfort (moq-dev/moq#8055): moq-cli's play metrics report it.
  • moq-mux TS Export::new and Release::new take the delay positionally (moq-dev/moq#7981), replacing with_delay. That PR narrowed the surface itself.
  • @moq/watch Video.SourceInput.rate replaces probe (moq-dev/moq#8061).

Merging origin/main again (c02ace3) brought more new public API. None breaks the epic's rules, so this merge changes none:

  • @moq/net Track.Producer.skipGroups and Error.Skipped (moq-dev/moq#7670): they mirror Rust track::Producer::skip_groups and Error::Skipped. moq-archive, moq-c, and moq-ffi call skip_groups.
  • moq-mux codec::h265::{Sps, VideoSignal} (moq-dev/moq#8140): moq-video's VUI decode calls Sps::parse. NalType is pub(crate).
  • kio clock::host_time (moq-dev/moq#8081): tests and binaries that need the host clock call it.
  • moq-tokio and moq-uring quic::web::Error (moq-dev/moq#8167): maintainer decision 3.
  • moq-net socket servers and clients (moq-dev/moq#8115): adds no new pub items. Its JS side now uses this PR's Socket name.

Merging origin/main a third time (6f66d55) brought more new public API. None breaks the epic's rules, so this merge changes none:

  • moq-audio bus strip set_pan and pan (moq-dev/moq#8180): a new mixer feature, next to the strip's gain controls.
  • moq-net origin::Producer::with_proxy and origin::Consumer::with_proxy (moq-dev/moq#8023): moq-relay's cluster, moq-stats, and tests call the Producer one. Only tests call the Consumer override, so it is a suggested follow-up below. The route layer itself (route::Node and friends) stays crate-private.
  • qmux APP_LIMITED_MASK (moq-dev/moq#8170) is inside a private module.

Merging origin/main a fourth time (181e8bb) brought no new public API, so this merge changes none:

  • moq-webrtc vendors and trims str0m and its stack (moq-dev/moq#8163) into the private vendor module (str0m is only pub(crate) in it), so no public item exposes a vendored type and str0m left the dependency graph. That narrows moq-webrtc's public dependencies.
  • moq-dev/moq#8190 and moq-dev/moq#8185 change only quests and a test quarantine.

Kept public on purpose

  • The m3 bake-off surfaces and moq_crypto::sign (maintainer, 2026-11-06, above).
  • moq-stats' aggregate group reader (maintainer, 2026-11-06).
  • moq_auth::mesh Client, Signer, and the coordinator handlers (decided 2026-11-02).
  • moq-audio aec (moq-dev/moq#7369).
  • Snapshot.Config.maxGroupBytes (moq-dev/moq#7072).
  • @moq/hang Health, which moq.pro's m2 qos quests plan to consume.
  • @moq/watch Broadcast.include (decided 2026-11-04).
  • MoqTrackInfo.{max_age, max_age_unlimited}: UniFFI 0.32 can't give a Duration a zero default.
  • moq_net::{Reconnect, Server, Session} root re-exports: one pattern, so all or none.

Refuted findings:

  • moq_json::window::Producer::abort and its JS mirror: moq-c and moq-ffi call it.
  • bandwidth::Demand and Producer::demand: moq-tokio's RecvBandwidth uses them.
  • Rust transport::StreamCredit: moq-net's open line uses it.

In-flight PRs

Impact

Public API: breaking in every area listed under Changes. Wire: none; error codes and session codes are unchanged.

Migration notes:

  • @moq/hang:
    • Report.PRIORITY: removed.
    • Report.Writer.wanted: removed; use writer.track.demand().wanted.
    • Container.DEFAULT_MAX_BUFFER: removed. maxBuffer is internal.
    • Catalog.Audio.{Audio, AudioConfig, AudioSchema, AudioConfigSchema} and Catalog.Video.*: use the flat Catalog.* names. ranked is unchanged.
  • @moq/boy: LatencyEntrySchema: removed. The LatencyEntry type stays.
  • @moq/ui:
    • createFullscreen and Fullscreen: removed; use fullscreenButton.
    • GraphOptions.{max, samples}: removed.
  • @moq/publish:
    • Fanout is type-only and has no close(). FanoutProps: removed.
    • Broadcast.STATS_TRACK: removed; read catalog.stats.track.
    • The listed element properties are readonly.
  • @moq/qmux: Session.streamCredit and StreamCredit: removed.
  • @moq/net:
    • Datagram.Request: removed.
    • Group.Frame{Info, Producer, Consumer} -> Frame.{Info, Producer, Consumer}.
    • socket -> Socket.
    • IMPLEMENTATION: removed; pass your own implementation.
    • Path.MAX_BYTES: removed (4096).
  • moq-net:
    • moq_net::Datagram -> moq_net::datagram::Datagram.
    • Error::{EmptySegment, InvalidEscape} -> Error::Path(path::Error::{EmptySegment, InvalidEscape}).
    • auth::Handle::add(t).await returns Credential. auth::Answer: removed.
    • auth::Request::{kind, path} and RequestKind: removed.
    • auth::Tokens::{dial, connected, changed} and auth::Dial: removed; use Reconnect::with_tokens.
    • Removed: Session::{poll_idle, idle}, Version::has_setup_token, track::Producer::abort_unwanted, datagram::{Producer, Consumer}::is_clone, socket::Dialing::session, and stats::Activity::WINDOW.
    • datagram::Info::produce: removed; use broadcast.create_datagrams(name, info).
  • moq-auth: removed PeerGrant, Key/KeySet::{sign_peer, verify_peer}, KeySet::to_peer_set, and Error::{NoPeerKey, PrivatePresenter}.
  • moq-webrtc: Error gains InvalidPath.
  • moq-quic:
    • moq_quic::{CreditStats, FrameStats, PathStats, UdpStats, Streams, Datagrams} -> moq_quic::connection::*.
    • connection::{send, recv} -> moq_quic::{send, recv}.
    • Event and Connection::poll: removed.
    • transport::Config.max_outgoing_bytes_per_second: removed; use Limits.send_rate.
    • send::Stream::poll_acked and Connection::poll_handshake_confirmed: removed; use acked.
  • moq-congestion:
    • Init.max_pacing_rate: removed.
    • moq_congestion::{Gcc, Scream} -> gcc::Gcc and scream::Scream.
    • {Bbr3, Cubic}: removed; use bbr3::Config and cubic::Config through Factory::build.
  • moq-tokio:
    • moq_tokio::quic::driver::{CreditStats, FrameStats, PathStats, UdpStats} -> moq_quic::connection::*.
    • tcp::Listener::accept and websocket::Listener::accept return Result<Session>.
    • --listen-implementation / MOQ_LISTEN_IMPLEMENTATION / listen.implementation: set listen::Config.implementation in code.
    • --connect-backoff-healthy / MOQ_CONNECT_BACKOFF_HEALTHY / connect.backoff.healthy: set Backoff.healthy in code.
    • listen::Forward::configured: removed.
  • moq-sock: demux::{classify, Class} and Queue::SHARE: removed.
  • moq-uring: Config.passes: removed; a turn runs at most four task passes.
  • moq-relay: the uring module: removed.
  • hang: Catalog::media() -> Catalog::without_ext().
  • moq-rtmp: Client::with_max_delay and Play::with_max_delay -> with_delay.
  • moq-srt: Client::with_max_delay -> with_latency, the same as release.
  • moq-mux:
    • fmp4::Import::with_passthrough -> with_cmaf.
    • container::Consumer::set_max_buffer and DEFAULT_MAX_BUFFER: removed.
    • fmp4::{MAX_HELD_BYTES, MAX_METADATA_BYTES, MAX_FRAGMENT_SAMPLES, MAX_TRACKS}: removed; the limits are documented on Import::decode and fmp4::Error.
  • moq-hls:
    • import::Config::passthrough -> cmaf.
    • export::Broadcaster::is_clone: removed.
    • Rendition::{interval, segment_length}: removed; read media.join.interval.
  • moq-stats: consume::{Consumer::group, GroupConsumer}: removed; subscribe to group_track(..) and decode GroupFrame.
  • moq-cli: hidden auth serve --mtls-peer / --mtls-upstream: removed; use --mtls-link peer|upstream.
  • moq-ffi and every binding:
    • MoqFill.start_group() / start_frame() -> start().group / start().frame, and end_group() -> end(). Python: fill.start.group. Go: Start().Group. Swift: start.group.
    • MoqTrackRequest.abort and MoqDatagramRequest.abort -> reject. Go: Abort -> Reject.
  • Go:
    • GrantConsumer.Close and BandwidthConsumer.Close -> Cancel.
    • json.WindowPush/Pop/Skip -> WindowEventPush/Pop/Skip, and TimedWindowEvent -> WindowTimed.
  • Python:
    • moq.json.WindowPush/Pop/Skip -> WindowEventPush/Pop/Skip.
    • TrackProducer.video(b, init, lower=p) -> video(b, VideoInit(..., lower=p)), and the same for TrackStreamProducer.
  • Swift: video(broadcast:initData:target:lower:) -> VideoInit(..., lower: .track(p)).

Stale MIGRATE.md lines the release cut should fix (only the cut edits them):

  • rs/moq-rtmp/MIGRATE.md:42,54,55 -> with_delay.
  • rs/moq-srt/MIGRATE.md:36: drop it, since the name is back to with_latency.
  • rs/moq-hls/MIGRATE.md:46,59,128.
  • rs/moq-mux/MIGRATE.md:207 -> .with_cmaf(true).
  • rs/moq-cli/MIGRATE.md:34 -> --cmaf.
  • rs/moq-relay/MIGRATE.md:121 -> --mtls-link peer.

Downstream code that breaks on the next bump:

  • moq.pro rs/edge/src/rtmp/mod.rs:456 and :864: with_max_delay -> with_delay.
  • moq.pro agent/tests/test_voice.py:764,767 and agent/tests/test_voice_session.py:179: moq.json.WindowPush -> WindowEventPush.

Choices made without asking

  • JS Fanout is exported as a type only. A @internal constructor fails the repository's constructor check, and video/capture.ts subclasses it.
  • Backoff.healthy stays as a code-only field rather than being deleted. The reconnect integration tests need a sub-100ms healthy bar.
  • Item skipped, RenditionStats into hang Stats.Rendition: the hang schema's u53 brand doesn't match the encoders' plain numbers (suggested follow-up).
  • Item skipped, a single nullable max_age: UniFFI limits make it impossible (recorded above).
  • stream_stopped is quarantined (see Validation). Its sh/gh/test-inventory.allow entry names the existing m0 epic quest/m0/test-flakes-2/README.md as the owner, since the inventory requires an owning quest and only /quest-plan creates one. The follow-up below gives it its own child.

Validation

This round merged origin/main three times, up to c02ace3, which includes the queue's conflict at 17:50 UTC.

Latest iterate: merged origin/main 181e8bb into 2ee2cc4 as ae8d2bd. Only the quest link list in quest/m4/README.md conflicted, and the local quest merge driver resolved it. git diff origin/main...HEAD differs from the reviewed head's diff only in index hashes, hunk offsets, and context lines. quest check passes, and quest gate just ci passed every lane on ae8d2bd (Swift skipped as below).

Previous iterate: merged origin/main 6f66d55 into 2ee2cc4. The conflicts were in quest prose only (quest/m1/README.md and quest/m2/recut-held.md now drop both api-audit and the finished session-policy from the held list) and in sh/gh/test-inventory.allow, which keeps main's lite_tracks_interop entry and this PR's stream_stopped quarantine. Outside those lines, git diff origin/main...HEAD matches the reviewed head's diff. quest check --fix passes, and quest gate just ci passed every lane on 2ee2cc4 (Swift skipped as below). Main has since added d389cfb (#8185), which merges conflict-free.

  • Merge conflicts were in doc/lib/js/net.md, js/net/src/internal.ts, and js/net/src/socket.test.ts. The resolution keeps main's socket server and client roles (moq-dev/moq#8115) and stream-error imports, under this PR's Socket and Frame names.
  • moq-tokio quic::driver::tests::stream_stopped failed the land fixer's just ci base, and it fails on main too. The client side has a 100ms wall-clock timeout. When that fires, it drops the connection, and the server's accept unwrap at tests.rs:1684 panics on ConnectionClosed(APPLICATION_ERROR).
    • Branch: 500 of 500 passed with cargo nextest run --stress-count 500 on a quiet host. Pinned to one core with 40 busy loops, 30 of 30 failed with that panic.
    • main c3f5a1b, under the same contention: 28 of 30 failed, and 20 of 20 quiet runs passed.
    • Per AGENTS.md, it is now #[ignore]d and listed in sh/gh/test-inventory.allow. The fix is a suggested follow-up.
  • quest gate nix develop --command just ci origin/main ran twice:
    • c3a6a90: every lane passed except check gh. Its test inventory required an owning quest for the new #[ignore]. The allowlist entry fixes that, and sh/gh/test-inventory.py now passes.
    • fbd1c1c: every lane passed, including test rs (11,532 passed, 148 skipped), except test js. That failure was the @moq/net test "integration: lite-07 reports a relative start over a frameless newest group", which predates this PR. The full js/net suite (bun run test) failed it in 1 of 5 runs here and 2 of 10 on main 31f19c7. Alone it passes 300 of 300. moq-dev/moq#8185 quarantines it.
  • CI's Check on the old head 1fa1341 failed only "lite-07 answers a bounded window that resolves empty and FINs in reply". With bun test --rerun-each 300, it failed 18 of 300 times on both this branch and main. moq-dev/moq#8150 quarantined it, and that fix is merged in here.
  • Since fbd1c1c, the branch only merged c02ace3 conflict-free (test(net): quarantine the lite-07 empty-window reply flake and plan its fix #8150) and removed a doubled blank line in the allowlist. sh/gh/test-inventory.py and quest check --fix pass.
  • Swift is still not compiled. just ci prints "swift check: no swift toolchain on PATH, skipping", since this host has no macOS toolchain, and PR CI doesn't run Swift either. The Swift Fill/reject renames and the VideoInit wrapper need macOS. Suggest dispatching gh workflow run platform.yml --repo moq-dev/moq --ref quest/m1/api-audit/README before landing.
  • Browser checks are left to CI (the maintainer asked for no headless browsers on this host): just test harness, the Playwright/Chromium/Firefox suites, just bench-browser, the browser media rows (just test media), and interop's js browser client.

Earlier runs, on 9aa0ce3:

  • just check origin/main on the merged head (twice, the second time after the passes change and a fresh main merge): every lane passed except test rs. Its one failure was moq-relay uring::tests::a_group_stopped_before_serving_joins: io_uring setup hit the per-user RLIMIT_MEMLOCK (8 MiB) that every process on this busy host shares. The second run also hit it in pending_admission_is_abandoned_at_the_deadline. A serial cargo nextest run -p moq-relay -p moq-uring --all-features --test-threads 1 --no-fail-fast -E 'test(/uring/) | package(moq-uring)' passed 184 of 184 on the branch, as it did on main.
  • Each fixer's just check passed on its own branch.
  • just test interop --all --publishers rust,python,go,cpp --subscribers rust,python,go,cpp,js-native-node,js-native-bun,c,gst: all 182 rows passed, after fixing the Go interop client's stale grants.Close. This is the full matrix minus the browser js client.

Suggested follow-ups

  • Type @moq/publish Rendition.stats as hang's Stats.Rendition: let Report.Writer take its schema's input type, or drop the u53 brand from the stats schemas.
  • Fix moq-tokio quic::driver::tests::stream_stopped at its cause and un-ignore it, as a child of quest/m0/test-flakes-2. It bounds the client with a 100ms wall-clock timeout, so a loaded host fails it, while nextest's 60s terminate-after already bounds a hang. The likely fix is to drop that timeout, or assert on the stopped event instead of a deadline.
  • Drop origin::Consumer::with_proxy (moq-dev/moq#8023) in favor of Producer::with_proxy, which already reaches every consumer taken from it. Only tests call the Consumer override, but the moq-net harness passes it about 195 scoped consumers, so collapsing it is its own change. Recommend planning it under this epic's successor.
  • Clean up inherited moq-quic surfaces with no consumer: endpoint::{Accepting, Accepted}, start_accept / finish_accept, Connection::{side, is_handshaking}, and stream::Id::{new, dir, initiator}.
  • Rename the Dart MoqRoute.instanceId() and MoqAudioEncoderFinished once moq moves to rust2's UniFFI fork (quest/m3/c).

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1b827-845d-7160-85fc-3f14ecc2a146, round 1
  • Head: 2ee2cc4
  • Base: origin/main at d389cfb
  • Score: 9/10, Source review found no actionable behavioral regressions in the scoped changes; builds and tests were not run, as instructed.
  • Findings: none

ae8d2bd only merges origin/main 181e8bb into the reviewed head 2ee2cc4. Its git diff origin/main...HEAD matches the reviewed head's diff apart from context and offsets, so this review covers it.

The first attempt to resume the previous session failed (quest asked it to settle findings from a clean round), so this round started a new session with --new.

Pullfrog's review of 1fa1341 found PR-body claims that predated the epic's deletion, plus the moq-rtc to moq-webrtc rename. Fixed in this body.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 14 commits November 6, 2026 14:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Narrow the public surface of moq-quic, moq-congestion, moq-sock, moq-tokio,
moq-uring and moq-relay to what has a consumer:

- moq-quic: send/recv live at the root only; stats, Streams and Datagrams
  under connection only. Remove connection::Event and Connection::poll,
  transport::Config.max_outgoing_bytes_per_second, and make poll_acked and
  poll_handshake_confirmed test-only.
- moq-congestion: drop the root Bbr3/Cubic/Gcc/Scream re-exports, make
  Bbr3 and Cubic crate-private, and remove Init.max_pacing_rate, which
  nothing set once the transport knob went.
- moq-sock: demux::{classify, Class, Queue::SHARE} are private.
- moq-tokio: listen.implementation and connect.backoff.healthy are
  code-only; tcp and websocket Listener::accept return Result; Forward::configured
  is private.
- moq-relay: the uring module is crate-private.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- hang: Catalog::media() -> without_ext(), so `media` names only the catalog's media section.
- moq-mux: container::Consumer::set_max_buffer and DEFAULT_MAX_BUFFER go crate-private;
  fmp4::{MAX_HELD_BYTES, MAX_METADATA_BYTES, MAX_FRAGMENT_SAMPLES, MAX_TRACKS} go private;
  fmp4::Import::with_passthrough -> with_cmaf, matching the CLI's --cmaf.
- moq-hls: import::Config.passthrough -> cmaf; Broadcaster::is_clone and
  Rendition::{interval, segment_length} go crate-private.
- moq-rtmp: Client/Play::with_max_delay -> with_delay (the FLV release delay, CLI --delay).
- moq-srt: Client::with_max_delay -> with_latency (the SRT receive latency, CLI --latency).
- moq-stats: consume::Consumer::group and consume::GroupConsumer become test-only.
- moq-cli: drop the hidden --mtls-peer/--mtls-upstream flags, which never shipped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Datagram lives only at `moq_net::datagram::Datagram`; the root re-export is gone.
- `Error::{EmptySegment, InvalidEscape}` fold into `Error::Path(path::Error)`.
  A too-long path still maps to `BoundsExceeded`, so no wire code changes.
  moq-rtc answers an invalid WHIP/WHEP path through its own `Error::InvalidPath`
  instead of matching moq-net variants, which also makes a too-long path a 400.
- `auth::Handle::add` returns the `Credential` alone; `auth::Answer` is private.
- `auth::Request::{kind, path}` are removed and `auth::RequestKind` is private.
- `auth::Tokens::{dial, connected, changed}` and `auth::Dial` are crate-private.
- `Session::poll_idle` is crate-private and loses its `idle` wrapper; its
  integration tests move into a unit test.
- `Version::has_setup_token`, `track::Producer::abort_unwanted`,
  `datagram::{Producer, Consumer}::is_clone`, `datagram::Info::produce`, and
  `stats::Activity::WINDOW` are crate-private; `socket::Dialing::session` is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`PeerGrant`, `Key`/`KeySet::{sign_peer, verify_peer}`, `KeySet::to_peer_set`,
and `Error::{NoPeerKey, PrivatePresenter}` had no consumer: P2P trusts its
peers, so relay-issued peer grants wait for a real per-peer scope use case.
Removes the "Peer grants" doc section and the quest that planned this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ADME

# Conflicts:
#	quest/m3/release/npm-first-publish.md
…er events

- MoqFill::{start_group, start_frame, end_group} are start() -> MoqPosition and
  end(), like track::Fill.
- MoqTrackRequest::abort and MoqDatagramRequest::abort are reject, like
  track::Request and datagram::Request.
- Go GrantConsumer and BandwidthConsumer Close is Cancel, like every other
  Go stream handle.
- Python and Go JSON window events are WindowEventPush/Pop/Skip, and Go's
  TimedWindowEvent is WindowTimed, matching Kotlin and Dart.
- Python and Swift VideoInit carry the lower rendition as a wrapper producer,
  as Go's does, so TrackProducer.video and TrackStreamProducer.video drop
  their separate lower argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated and others added 2 commits November 6, 2026 16:14
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title A smaller, safer API than release refactor!: the final API audit narrows surfaces added since the audit Nov 7, 2026
kixelated and others added 2 commits November 6, 2026 16:58
The pass budget was public only for the worker_turns 1-versus-4 sweep.
Four is the value #7530 chose from its 1/2/4/8 sweep, and its ablation
showed one pass splitting each wake chain across turns. The finished API
audit epic is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ADME

# Conflicts:
#	js/qmux/src/index.ts
#	js/qmux/src/session.ts
@kixelated
kixelated marked this pull request as ready for review November 7, 2026 01:42

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No code findings. A handful of prose references still name surfaces this PR removes, including MIGRATE lines the Impact list for the cut does not mention.

Reviewed changes

  • JS: Frame.{Info,Producer,Consumer} split out of Group, socket → Socket, Catalog.Audio/Video reduced to ranked with flat config types, and Report.PRIORITY, Writer.wanted, DEFAULT_MAX_BUFFER, IMPLEMENTATION, Path.MAX_BYTES, LatencyEntrySchema, Fanout's constructor/close, Datagram.Request, and Session.streamCredit unexported or @internal.
  • Rust net/auth/crypto: moq_net::Datagram given one path, Error::Path(path::Error), Handle::add returning only Credential, and Answer/RequestKind/Request::{kind,path}/Tokens::{dial,connected,changed}/Session::{idle,poll_idle}/Activity::WINDOW/datagram::Info::produce narrowed or removed; the peer-grant API deleted with its quest.
  • Transport/relay: moq-quic reduced to one path per type with its event stream removed, Bbr3/Cubic and the pacing-rate knob privatized or deleted, moq-sock demux internals and moq-relay's uring module privatized, tokio listener accept returning Result, and moq-uring's pass budget fixed at four.
  • Media/gateways: Catalog::without_ext(), with_delay/with_latency/with_cmaf renames, and the mux/HLS/stats buffer, limit, and aggregate-reader knobs privatized.
  • Bindings: Fill.start()/end() and request reject mirrored through moq-ffi, Go, Python, and Swift, with Python/Swift VideoInit wrappers so lower is set once.
  • Quests: the epic deleted, its two citers pointed at a pinned revision, and @moq/ui added to the m3 npm first-publish quest.

ℹ️ Stale references to the APIs this PR removes

The Impact section's "Stale MIGRATE.md lines the release cut should fix" list is what the cut relies on, and it misses three files whose content this PR invalidates; two more prose references name a removed method.

Technical details
# Stale references after the removals

## Affected sites
- `rs/moq-relay/MIGRATE.md:139-141,251-253` — still tell embedders to use `uring::Workers::spawn`/
  `serve`/`bind`, but `moq_relay::uring` is now `pub(crate)`; §"io_uring workers start serving
  separately" and §"Embedding: io_uring workers bind from the server config" need dropping or
  rewriting to the public `Relay`/`Web` path.
- `rs/moq-quic/MIGRATE.md:44,52` — the migration table maps `Event::HandshakeConfirmed` and
  `StreamEvent::Acked` to `Connection::poll_handshake_confirmed` and `send::Stream::poll_acked`,
  which this PR makes `#[cfg(test)]`; the surviving answers are `poll_connected` and
  `Stream::acked`.
- `rs/moq-quic/README.md:87` — the "ACK progress" bullet still lists `poll_acked` as a wait API;
  the "Waits" bullet in the same file was updated to drop it.
- `rs/moq-net/MIGRATE.md:17,269` — names `Activity::WINDOW` (now private) and
  `Error::EmptySegment` (now `Error::Path(path::Error::EmptySegment)`).
- `rs/moq-net/src/session.rs:1018` — `[`Session::idle`]` is now a broken link; the method is
  `Session::poll_idle`, and is private.
- `quest/m3/goaway-migration-followups.md:9` — the JS early-close follow-up says "mirroring
  `Session::idle` / `Session::poll_idle`"; only `poll_idle` remains.

## Required outcome
- The Impact's stale-MIGRATE list covers every MIGRATE line this PR invalidates.
- No in-tree doc or quest names an item this PR removed or made private.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed commit 9aa0ce3 against base 1a1940c. No actionable correctness findings in the changes inspected.

Focused on the JS frame extraction and import dependencies; auth request/grant handling and path-error mappings; FFI and language-wrapper API migrations; CMAF/latency/delay option plumbing; and transport surface removal. The frame implementation is preserved through the move, removed PathUpdated events were discarded by both runtime drivers, and Limits.send_rate remains enforced independently of the congestion pacer. The intentional breaking changes and documented maintainer decisions were treated as the agreed scope.

Verification limits: GitHub-only static review. I did not run builds, tests, Swift/browser checks, interop, or benchmarks, and did not independently repeat the full public-API census or external downstream searches. The exact-head Check workflow is still in progress: https://github.com/moq-dev/moq/actions/runs/42325488378. This is not runtime validation or merge approval.

(Written by review (OpenAI))

kixelated and others added 7 commits November 7, 2026 12:33
# Conflicts:
#	doc/lib/js/net.md
#	js/net/src/internal.ts
#	js/net/src/socket.test.ts
It fails on main too when the host is loaded: the client's 100ms
wall-clock timeout fires and the server's accept unwraps ConnectionClosed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-2 epic

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@kixelated kixelated left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Automated review by review (OpenAI)

Reviewed head 51747bb against base c02ace3, incrementally since d804085 and accounting for merged main changes.

No new actionable findings. The production API changes remain equivalent to the prior review. Socket conflict resolutions preserve main's client/server roles and stream-error imports under the Frame/Socket names, including its new origin-close test. The epic and reference removals now follow the last child's merge in #8121. The new stream_stopped quarantine has the matching inventory entry; its existing 100 ms client deadline is consistent with the documented load-sensitive failure.

Direction: retain the migration guidance and complete the quarantined test's causal fix. This pass does not repeat the API census or certify every surface added on main.

Verification limits: GitHub-only base-aware comparison of all 206 old and 212 new file patches, with focused merge-resolution, quest and test inspection. No builds, tests, Swift/browser checks, interop, benchmarks or downstream searches run.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

Since the previous pullfrog review at 1fa13412b1:

  • Quarantined the flaky stream_stopped test: moq-tokio's quic::driver::tests::stream_stopped is now #[ignore]d (its 100ms wall-clock budget flakes under load and it fails on main too), with a sh/gh/test-inventory.allow entry naming quest/m0/test-flakes-2/README.md.
  • Merged origin/main (c02ace3017, conflict-free): brings #8115 socket roles, #7670 skip/stale drops, #8150's lite-07 quarantine, #8157, #8167, #8162, #8140, #8081, and the quest updates; the branch's Socket/Frame renames and API-audit removals are preserved.
  • Dropped a doubled blank line in the allowlist.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

…ADME

# Conflicts:
#	quest/m1/README.md
#	quest/m2/recut-held.md
#	sh/gh/test-inventory.allow
@kixelated
kixelated added this pull request to the merge queue Nov 7, 2026
Merged via the queue into main with commit 20981ed Nov 8, 2026
9 of 10 checks passed
@kixelated
kixelated deleted the quest/m1/api-audit/README branch November 8, 2026 00:09
kixelated added a commit that referenced this pull request Nov 8, 2026
Fix or clarify each automated review finding on the sweep's quest edits,
and fold #8088's with_proxy follow-up into api-shape/net.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Nov 8, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated added a commit that referenced this pull request Nov 8, 2026
#8088 narrowed it to private; the corpus test signs raw payloads with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant