Repository navigation
feat(net): a timed track refuses a group that rewinds - #5803
Conversation
A timed track now refuses a group whose first frame starts before the nearest checked group below it or after the nearest one above it, with TimestampMismatch. An equal start is allowed and untimed tracks are exempt. The check runs where frame 0 is stamped, atomically with the stamp, so every producer refuses: local writes, relays, and Rust and JS subscribers. Eviction keeps a one-entry watermark (the highest-sequence evicted start), so an evicted predecessor still bounds a later group. A late group at or below it is capped by the watermark and accepted unverified, which keeps it out of every bound and out of media-time expiry. A received rewind closes the track and keeps the session. The lite draft states the rule and broadens TIMESTAMP_MISMATCH; no wire change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Paper trail: implemented (Written by Opus 5.5) |
…rate # Conflicts: # rs/moq-net/src/model/track.rs
Lite-05/06 stamp an untimed track's frames with their send time, which needn't rise with sequence: concurrently served groups go out in any order, and a relay or second publisher stamps from its own clock. A receiver can't tell send times from media time, so a group received over lite-05/06 skips the rewind check and never bounds its neighbors, in Rust and js/net. Local writes, lite-07 and IETF ingest still refuse rewinds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
|
Warning Review limit reachedYou'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 38 minutes. View limit details
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. Comment |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40c9daa343
ℹ️ 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".
Cross-scale products can pass 2^53 and round alike, letting a real rewind through. Compare them in BigInt, as Rust does in u128, with fractions decomposed into exact binary mantissas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 635dd074de
ℹ️ 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".
… check Its first frame is a later one, whose timestamp says nothing about where the group began. Matches Rust, where start_at leaves the group unchecked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4016e601d8
ℹ️ 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".
A received timestamp may not be media time: lite-05/06 untimed tracks carry send times, and released peers stamp wall-clock time on data tracks. A relay that checked received groups could close a track that never rewound its media, so sessions mark their track copies as received and skip the check at every version. Received groups still drive media-time expiry. Drops the per-group skip (lite-05/06 and partial groups) and the ingest paths that closed the track on TimestampMismatch. Provenance moves to quest/m1/lite-time-provenance.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2bd83972b
ℹ️ 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".
A track name keeps one sequence namespace for its broadcast's life, so a replacement producer continues the same timeline. Its rewind check only saw its own cache, so it could start a group before the last one its predecessor wrote. The namespace now keeps the highest-sequence checked start, and a producer joining it takes that as its eviction watermark. A new broadcast starts a fresh namespace, so nothing crosses instances. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51f924cffe
ℹ️ 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".
A first frame found its neighbors' starts by walking cached slots until one was stamped, so N groups created before any was stamped cost O(N^2) to stamp in order. The track now keeps the checked starts in a BTreeMap, filled when frame 0 becomes a bound and pruned as slots leave, so the lookup is O(log N). `track_stamp_groups/sparse` covers the case: 441 ns / 2.94 us / 27.6 us at 64 / 512 / 4096 groups before, 165 / 199 / 229 ns after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0e3ab33ff1
ℹ️ 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".
Subscribers may have seen an aborted group's start, but replacing it at the same sequence dropped its bound without raising the eviction watermark, so a later group could start before it. Rust's claim_sequence now uncaches, and js/net's writeGroup evicts the old incarnation as a watermark before checking the replacement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review (Written by Opus 5.5) |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Port the rewind tests from moq_net_sim to moq_clock, and move the rewind note from doc/setup/upgrade.md into rs/moq-net/MIGRATE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main added moq-cli play tests that restart a track's timestamps after a discontinuity, which a local write now refuses as a rewind; they resume forward instead. catalog_now_fixes_the_clock_for_later_media ran on a paused runtime while the FFI runtime is unpaused, and each counts Timestamp::now() from its own jittered anchor, so catalog snapshots written from both rewound about half the time. It never advances time, so it runs unpaused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Narrow the rewind check's model-only items to pub(super) and mark track::Producer::received and hooks.receiveTrack pending on the public ingest quest (#5794). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pull request was converted to draft
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 128b0626d6
ℹ️ 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".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nchor quest Every reader gets Old when an unfinished group expires, and the hole budget reuses the subscription's max delay. Adds the process-wide Timestamp::now() anchor quest from #5803. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - cpp/moq/include/moq/moq.hpp: take main's #5727, which generates unprefixed names, dropping the alias list (and this branch's Anchor/Order/Range aliases, now generated directly). - rs/moq-net/src/model/track.rs: keep both appended test sets (subscription ranges here, rewind refusal from #5803). Semantic follow-ups for callers main added against the old API: - rs/moq-net/tests/integration/received_rewind.rs: with_start(group 0) becomes with_groups(0..), as this branch migrated other callers. - py/moq-rs/tests/test_testing_clock.py: group_start=0 becomes an absolute group-0 range. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…5871 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also guard Go's nil receive-bandwidth consumer in the #5777 snippet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Completes the
quest/m1/refuse-rewinds.mdquest, deleted here: a timed track refuses a group whose first frame rewinds, on a local publisher's writes, in Rust and js/net. Follows the decision in #5446.What changed
TimestampMismatch. Groups land out of sequence order, so both neighbors count. An equal start is allowed. Untimed tracks are exempt.write_frame,write_frames,open_frame; JSwriteFrame, pluswriteGroupfor a group stamped before it joined). It is atomic with the stamp: the track lock is held across the group lock, in the existing track-then-group order.track::Producer::received, crate-private; js/nethooks.receiveTrack), and a received track checks nothing. Received groups at every version (lite-05/06/07 and IETF) are accepted as is and still drive media-time expiry. A received timestamp may not be media time, so checking it on a relay hop could close a track that never rewound.start_at) is neither checked nor a bound.TimestampMismatch(stream code 0x39) is reused. Its doc and message are broadened to "frame timestamp doesn't fit its track".draft-lcurley-moq-litestates the publisher MUST and that receivers do not check it.reach/#successorno longer cite rewinds.rs/moq-net/MIGRATE.mdgains an Unreleased entry. The fmp4Import::discontinuitydoc notes that a restarting timeline now fails.quest/m1/lite-time-provenance.md: make received timestamps trustworthy enough to check across hops.reach_follows_the_immediate_successor_not_a_later_rewind,aborted_stamped_successor_wakes_a_parked_readand moq-archivea_decreasing_timestamp_fails_the_recording.a_failed_recording_flushes_what_was_reported. Its trigger, an in-session group-start rewind, can no longer reach the writer, and an ID-range overflow takes the read path, which flushes differently.now()out of order to use media time.a_received_rewind_is_accepted_at_every_version(lite-05/06/07),a_received_rewind_is_accepted(IETF draft-18), and js/netversion %p accepts a received rewind(lite-05/06/07).received_rewind::a_relay_forwards_descending_send_times_without_closing_the_tracksends an untimed track's groups 1 then 0 into a relay over lite-06 and out over lite-06, lite-07 and moq-transport-22; every subscriber reads both groups and the track stays open.track.test.ts.track_stamp_groupsstamps a group's first frame, swept over N cached groups.appendadds stamped groups to a track holding N stamped ones: 420 / 549 / 814 ns at 64 / 512 / 4096.sparsecreates N groups and then stamps them in order: 441 ns / 2.94 µs / 27.6 µs per stamp when neighbors were found by walking slots, and 165 / 199 / 229 ns with checked starts kept in a per-trackBTreeMapby sequence. js/net already binary-searches a sorted array of bound starts only, so it has no such scan.Public API and wire impact
TimestampMismatchkeeps its variant and code; only its doc andDisplaytext change.quest/m1/public-model/ingest.md, so a custom session built on the public model gets local-write checks on its track copies. Exposing it is left to the public-model quest, if a consumer needs it.EXT-X-DISCONTINUITYrestarts decode times now fails withTimestampMismatchinstead of continuing.Import::discontinuityno longer rescues a restarting timeline. Accepted until the follow-up quest restarts the import under a successor epoch.Decisions (settled by the maintainer)
quest/m1/lite-time-provenance.md.TimestampMismatch(0x39) with a broader message, instead of a new code.claim_sequenceuncaches; js/net'swriteGroupevicts the old incarnation before checking the replacement. (Earlier rounds dropped the bound; Codex P1 showed that let a later group rewind.)TimestampMismatch. Accepted as a regression; a follow-up quest restarts them under a successor epoch.Paper trail: why received groups are unchecked
just test interop --allfound that checking received groups broketail js-native-* -> rust(2 in 10 runs): lite-05/06 publishers stamp an untimed track's frames with their send time, groups are served concurrently, and a later-sent lower group closed the relay's track. Exempting lite-05/06 ingest (an earlier round) fixed that hop, but Codex found the relay then forwards those send times as media time over lite-07 or IETF, where the next hop's check closes the track. Released clients and relays also put wall-clock and arrival times on the wire, so no received timestamp is reliably media time today.Suggested follow-ups
quest/m1/lite-time-provenance.md: received timestamps are media time, so the check can run across hops.Import::discontinuityif nothing else needs it.reachto the first stamped group above, which the refusal makes safe, if a stall shows it matters.Checks
This round (local writes only):
just check(lint, compile and test what the branch changed): pass (6913 moq-net tests plus the other changed crates and js workspaces).TimestampMismatchwhen the received mark is ignored, and pass with it: Rust lite, IETF and relay integration; js/net lite.just test interop --all: 68 of 68 pass, including every tail lane.just drafts check: pass.quest check: pass.Codex P1 (replacement producers):
just checkpasses (6914 moq-net tests plus the changed crates and js workspaces). New testsreplacement_refuses_a_rewind_past_its_predecessor(Rust,model/broadcast.rs) anda replacement producer refuses a rewind past its predecessor(js/net,broadcast.test.ts) fail without the shared bound.Codex P2 (O(N^2) sparse stamping): confirmed and fixed with the bound index, numbers in the Bench bullet.
just checkpasses.Codex P1 (replaced aborted group): new tests
a_replaced_aborted_group_still_bounds_later_ones(Rust) anda replaced aborted group still bounds later ones(js/net) fail without the fix.just checkpasses (6915 moq-net tests plus the changed crates and js workspaces).Merge with main (7e90fb4):
moq-cliplay tests new on main restarted the timeline at 0 afterdiscontinuity(), which this PR's local-write check refuses. They now resume forward;just rs playpasses 215 of 215.catalog_now_fixes_the_clock_for_later_mediafailed about half its runs: itsstart_pausedruntime countsTimestamp::now()from its own jittered anchor while the FFI runtime uses the process anchor, so catalog snapshots written from both rewound. It never advances time, so it runs unpaused: 300 of 300 pass.moq_net_simtomoq_clock(Migrate network timing onto moq-clock #5681), and moved the upgrade note intors/moq-net/MIGRATE.md.just check: pass.Second merge with main (128b062), after #5794 narrowed the model: the rewind check's model-only items are
pub(super), andtrack::Producer::receivedandhooks.receiveTrackcarry// pending: /quest/m1/public-model/ingest.md, which now lists them.just check: pass.Codex P1 (fetch-only tracks unmarked): not reachable. Lite fetches run under a
ServeLoop, whose constructor accepts and marks the track first; IETF tracks are accepted by SUBSCRIBE or TRACK_STATUS, which both mark. Newreceived_rewind::fetches_alone_accept_descending_send_times(lite-05/06, fetch-only, descending send times) passes and fails ifServeLoop::newstops marking.just check: pass.Earlier rounds:
earliernow compares exactly in BigInt, as Rust does in u128. Kept, since local JS writes can mix scales. Testa rewind across large timescales is refused.cargo nextest runacross moq-net, moq-archive, moq-flate, moq-mux, moq-stats, hang, moq-ffi, moq-e2ee, moq-hls, moq-tokio, moq-relay, moq-srt, moq-loc, moq-room, moq-json, moq-rtmp, moq-c, moq-rtc, moq-uring, moq-gst, moq-video, moq-audio, moq-transcode and moq-boy: pass.cargo clippy -D warnings,cargo fmt, Biome,tsc --noEmit,just drafts check,just rs wasm: clean.(Written by Claude Opus 5.5)
🤖 Generated with Claude Code