Skip to content

fix(net)!: the application owns a replacement track's start sequence - #5068

Merged
kixelated merged 15 commits into
mainfrom
quest/m0/js-track-takeover
Oct 13, 2026
Merged

kixelated merged 15 commits into
mainfrom
quest/m0/js-track-takeover

Conversation

@kixelated

@kixelated kixelated commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

A track name always means the same content, so a producer that replaces an ended one under the same name must never reuse its group or datagram sequences. #4929 handled that in the library: Rust's producers, and JS's on-demand producers, silently continued from wherever the name's last producer stopped. JS createTrack didn't, so @moq/publish restarted its catalog at group 0 on every re-announce. It also restarted every rendition track whenever a rendition was registered or removed.

Direction from the maintainer: the application owns the start sequence. The library should not quietly start somewhere else. It should fail loudly when an app reuses a name in a way that would repeat sequences.

Approach

  • Rust and JS, same rule. A broadcast still records, per track name, one past the highest sequence any producer of the name wrote. An append no longer jumps ahead to that value. It takes one past the producer's own writes and refuses if that would reuse a written sequence: Error::Duplicate in Rust, Error("duplicate sequence: ...") in JS. A replacement picks its start by writing its first group with an explicit sequence (create_group / writeGroup), and later appends continue from there. Explicit writes stay unchecked, so a relay can still forward upstream sequences, including a group it had before. A new broadcast starts over.
  • JS createTrack answers a queued request, as Rust's create_track does. This is separate from sequences: without it, subscribers queued for a name stay stuck once the app creates the track.
  • JS insertTrack is removed. It has no Rust counterpart and no production caller. It would also bypass the name's sequence record.
  • JS sweeps unused names like Rust. Names that never wrote and that no open producer holds are swept as the record grows. There is no cap and no runtime refusal.
  • @moq/publish owns its sequences. Each track, catalog included, lives as long as the network broadcast. A re-announce, a rendition change, or re-registering a name keeps writing to the same producer. A maxAge change publishes a new network broadcast, because a track's retention is fixed when it is created. A rendition name keeps its kind within one network broadcast. A rendition track that closed (an encoder error) can't be continued, so replacing it publishes a new network broadcast, where the name starts over. This absorbs publish catalog restart, which is deleted.
  • JS Request.accept rejects on any failure, invalid info included, so a request is never left open with subscribers waiting.

Decisions

Settled 2026-10-09, still holding:

  1. A new name past the bound is a plain Error("too many track names"). Superseded by 8.
  2. The bound is 1024 names per broadcast. Superseded by 8.
  3. insertTrack is removed in this PR, not in a separate quest (confirmed 2026-10-11). ✅
  4. Taking over a request a handler already pulled stays duplicate, as in Rust. ✅

Settled 2026-10-11 (maintainer direction: the application owns the start sequence):

  1. Library behaviour when a replacement appends. Options: (a) continue the name's sequences silently (fix(net): one dynamic track per name, sequences continue across replacements #4929); (b) refuse an append that would reuse a written sequence, and leave explicit writes unchecked ✅; (c) refuse to re-create a written name at all. (c) breaks relays, which replace producers under the same name and write upstream sequences.
  2. How @moq/publish complies. Options: (a) keep each track for the network broadcast's life, and move to a new broadcast on maxAge ✅; (b) mint a fresh broadcast on every re-announce or rendition change; (c) carry sequences by hand into re-created tracks.

Settled 2026-10-11 by the maintainer:

  1. Replacing a rendition track that closed after writing. Options: (a) let the replacement's first keyframe throw; (b) have @moq/publish move to a new network broadcast, since a new broadcast is new content, in this PR ✅; (c) carry the closed track's next sequence into the replacement.
  2. The JS name bound. Options: (a) keep the 1024-name cap; (b) drop it and sweep unused names like Rust ✅. A runtime cap only adds a runtime error.

Impact

  • Public API (Rust moq-net): no signature change. Behavior change: track::Producer::append_group and append_datagram return Error::Duplicate instead of continuing past an earlier producer of the same name. This applies to create_track, reserve_track, and on-demand requests.
  • Public API (@moq/net): breaking. BroadcastProducer.insertTrack is removed. Track.Producer.appendGroup / appendDatagram throw duplicate sequence instead of continuing past an earlier producer of the name. createTrack answers a queued request instead of throwing duplicate track.
  • @moq/publish: tracks persist for the network broadcast's life. Rendition.close() no longer closes the network track. A maxAge change, or replacing a closed rendition track, recreates broadcast.net. Registering a name under a different kind on the same network broadcast throws.
  • @moq/net behavior: Request.accept closes the request's producer with the error whenever it throws.
  • Wire: none.
  • test/interop/clients/rust/tail.rs: formatting only. chore(interop): rustfmt the rust tail client #5377 reindented it with tabs, but no rustfmt config covers test/interop, so cargo fmt --all --check failed for any Rust change. Restored to its pre-chore(interop): rustfmt the rust tail client #5377 form.

Testing

  • Rust: replacement_refuses_reused_sequences, stale_producer, and route_change.rs producer_replaced_* (an app replacing an aborted producer continues explicitly).
  • JS net: the replacement and created/requested namespace tests now assert the throw and the explicit continuation. The sweep forgets names that never wrote and keeps written and held ones.
  • JS net: createTrack with invalid info rejects the queued request it answers.
  • JS publish: a re-announce continues the catalog's group sequence, a rendition keeps its track across rendition changes and re-registration, replacing a closed track publishes a new network broadcast, a name keeps its kind only within one network broadcast, and a maxAge change swaps the network broadcast. The re-announce, rendition-change, and closed-track tests fail without the fix.
  • just check. There is no wire change, so the interop suite is not affected.

Follow-ups

  • @moq/publish does not recover a rendition track that closes mid-stream until the rendition set changes, as on main. Restarting reactively on close would loop on a dead audio encoder, which closes every track it is handed, so recovery needs to decide which closes are recoverable.
  • Give test/interop/clients/rust a rustfmt config (or move rs/.rustfmt.toml to the root) so its formatting no longer depends on the environment.

(Written by Claude Opus 5.5)

🤖 Generated with Claude Code

kixelated and others added 2 commits October 8, 2026 16:29
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…quences

createTrack answers a request still queued for its name, and createTrack and
insertTrack continue the name's group and datagram sequences within the
broadcast, as Rust's create_track does. A broadcast remembers written
sequences for up to 1024 names; unwritten, unheld names are swept and a new
name past the bound is refused.

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

Copy link
Copy Markdown
Collaborator Author

Quest run outcome: implemented and just check passes locally. Left as a draft, with these maintainer decisions open:

  1. How a new name is refused past the bound. Options: (a) a plain Error("too many track names") thrown by createTrack/insertTrack/Request.accept, with the request rejected to its subscribers (implemented); (b) an exported error class or stream code so callers can match on it; (c) no hard bound, only Rust's sweep of unused entries. Recommend (a). Rust has no bound today, so (c) would match it exactly, but the quest asked for a bound.
  2. The bound value. 1024 names per broadcast, counting only names that wrote or are held open. Recommend keeping 1024.
  3. What insertTrack does with a queued request. It still throws duplicate track, because the waiting subscribers belong to the request's producer. Options: (a) keep it (implemented); (b) remove insertTrack, which has no Rust counterpart and no production caller (a breaking change); (c) move subscribers between producers (complex). Recommend (a) now, and consider (b) in a separate API-cleanup quest.
  4. Taking over a request a handler already pulled. It stays duplicate, as in Rust, so a later accept/reject from the handler never conflicts. Recommend keeping this.

(Written by Claude Opus 5.5)

kixelated and others added 2 commits October 9, 2026 11:17
…eover

Keep this branch's track-name behavior in the JS net doc and main's timestamp wording. The broadcast tests import both the sequence bound and textFrame, and write frames with textFrame.

Co-Authored-By: Grok 4.7 <noreply@x.ai>
createTrack is the only way to add a track by hand. It still answers a queued request, continues the name's sequences, and refuses a new name past 1024 with a plain Error. A request a handler already pulled stays duplicate.

Co-Authored-By: Grok 4.7 <noreply@x.ai>
@kixelated kixelated changed the title fix(net): JS createTrack takes over a queued request and continues sequences fix(net)!: JS createTrack takes over a queued request and continues sequences Oct 9, 2026
kixelated and others added 2 commits October 11, 2026 14:27
…eover

# Conflicts:
#	quest/m0/js-track-takeover.md
#	quest/m1/README.md
An append on a producer that replaces an ended one under the same name no
longer silently continues the name's sequences. It refuses (Rust
Error::Duplicate, JS "duplicate sequence") when it would reuse a sequence the
name already wrote; the application starts the replacement with an explicit
create_group/writeGroup. Explicit writes stay unchecked so relays keep
forwarding upstream numbering.

@moq/publish now keeps each track for its network broadcast's life, so a
re-announce or rendition change continues the same producer, and a maxAge
change publishes a new network broadcast. Absorbs the publish catalog
restart quest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title fix(net)!: JS createTrack takes over a queued request and continues sequences fix(net)!: the application owns a replacement track's start sequence Oct 11, 2026
@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T01:29:02.600857Z 91ceb54 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 611c590637

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/publish/src/broadcast.ts
A rendition named like a catalog track would otherwise reuse the catalog's
producer instead of failing as a duplicate.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a8b2a07ae3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/net/src/broadcast.ts
Comment thread js/publish/src/broadcast.ts
Invalid info left the request's producer open, so a queued request answered
by createTrack stranded its subscribers and held the name. Also document that
Rendition.close leaves the network track with the broadcast.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62f0672298

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/publish/src/broadcast.ts
Comment thread js/publish/src/broadcast.ts
A rendition track closed by an encoder error is recreated on re-register
instead of reused, and registering a name under a different kind throws.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f46a96dbe6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/publish/src/broadcast.ts Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 054b563c32

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/publish/src/broadcast.ts Outdated
kixelated and others added 2 commits October 11, 2026 18:40
…ew broadcast

@moq/net no longer refuses a broadcast's 1025th track name. Like Rust, it
sweeps names that never wrote and no producer holds as the record grows.

@moq/publish moves to a new network broadcast when a closed rendition track
must be replaced, since a replacement under the same name would restart its
sequences.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: fcf9cf4901

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

# Conflicts:
#	doc/lib/js/net.md
#	js/net/src/track.ts
#	quest/m0/broadcast-epoch/README.md
#	rs/moq-net/src/model/broadcast.rs
@kixelated
kixelated marked this pull request as ready for review October 13, 2026 00:48
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 15 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 519d2de9-d993-47fe-9e16-c6e88334b75c
📥 Commits

Reviewing files that changed from the base of the PR and between 6a3459c and 91ceb54.

📒 Files selected for processing (21)
  • doc/lib/js/net.md
  • doc/lib/js/publish.md
  • doc/lib/rs/moq-net.md
  • js/net/examples/publish.ts
  • js/net/src/broadcast.test.ts
  • js/net/src/broadcast.ts
  • js/net/src/internal.ts
  • js/net/src/track.ts
  • js/publish/src/broadcast.test.ts
  • js/publish/src/broadcast.ts
  • js/publish/src/rendition.ts
  • quest/m0/README.md
  • quest/m0/broadcast-epoch/README.md
  • quest/m0/broadcast-epoch/publish-catalog-restart.md
  • quest/m0/js-track-takeover.md
  • quest/m1/README.md
  • quest/m1/js-insert-track.md
  • rs/moq-net/src/model/broadcast.rs
  • rs/moq-net/src/model/track.rs
  • rs/moq-net/tests/route_change.rs
  • test/interop/clients/rust/tail.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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: bea1e2e

No actionable bugs found in the full PR diff and surrounding sequence, request, catalog, and encoder-lifetime code. The direction is coherent: applications choose replacement sequences, explicit forwarding remains available, and publish keeps tracks for the network broadcast's lifetime. Restarting the broadcast for retention changes or failed-track replacements avoids a second application-side sequence ledger.

The earlier findings about queued-request validation, catalog-name collisions, rendition-kind lifetime, and failed-track replacement appear addressed in the current code; I found no independent remaining instance to flag.

Verification limits: GitHub-only static review of implementation and regression tests. I did not run tests, builds, or browser/media playback, and this is not a test-pass claim.

(Written by OpenAI)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bea1e2e054

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread js/publish/src/broadcast.ts
kixelated and others added 2 commits October 12, 2026 18:08
#5377 reindented tail.rs with tabs, but no rustfmt config covers
test/interop, so `cargo fmt --all --check` wants rustfmt's default
spaces and fails for every Rust change. This restores the file as it
was before #5377.

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

Copy link
Copy Markdown
Collaborator Author

@codex review

(Written by Claude Opus 5.5)

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 91ceb540f9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@kixelated

Copy link
Copy Markdown
Collaborator Author

Merge summary for 91ceb540f9c84f5ada5e37c3a995377c2ae0bbb6:

  • Merged main twice. Conflicts in js/net/src/track.ts (main's track end and group cap merged with this PR's sequence check), rs/moq-net/src/model/broadcast.rs (this PR's tests on main's track::Info::timed()), doc/lib/js/net.md, and quest/m0/broadcast-epoch/README.md.
  • Restored test/interop/clients/rust/tail.rs to its pre-chore(interop): rustfmt the rust tail client #5377 formatting, since cargo fmt --all --check on main fails for any Rust change.
  • Decisions 3 to 8 are as settled in the description. All seven earlier Codex findings were fixed or answered. I declined the new P1 about reactive recovery of a closed rendition track: main behaves the same, and a reactive restart would loop on a dead audio encoder. It is listed as a follow-up.
  • Reviews on the final head: Codex found no major issues. The OpenAI review of the first merge found no actionable bugs.
  • just check passes locally.

(Written by Claude Opus 5.5)

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