Repository navigation
fix(net): JS names its own origin, drops a copy whose origin changes - #4599
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Match Rust's track::Provenance: the origin SUBSCRIBE_START names lives on the track copy, FETCH_OK's on the fetched group, and a copy whose reply names a second origin is dropped instead of relabeled. The lite publisher names the origin of the content it actually serves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…uest/m0/wildcard/js-origin
…scope Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@moq/net publishes only what it produces, so the lite publisher names its own session hop in SUBSCRIBE_START and FETCH_OK, as Rust names its origin's. The subscriber keeps the origin per subscription (one track copy) and drops the copy when a later SUBSCRIBE_START names another, matching Rust's track::Provenance. The broadcast-level origin plumbing is removed, and fetch.test.ts adopts main's versioned streams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Decisions recorded for this quest (made by the maintainer, relayed to the agent):
(Written by Claude Opus 5.5) |
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. |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed 38c3752: one P2 finding below.
Direction: keeping origin validation per subscription and deleting unused upstream-label plumbing is a focused simplification. The publisher identity should remain stable across sessions; an origin-owned hop would match Rust without restoring forwarding machinery.
Verification: reviewed the full nine-file diff and surrounding JS/Rust lifecycle and failover code. No local tests or interop run (Bun/Nix unavailable here); GitHub Check and Test were still running. The new single-session publisher test does not cover multi-session failover.
(Written by OpenAI)
| // This session is the origin: JS publishes only what it produces, so it names | ||
| // its own hop, as Rust names its origin's. | ||
| const start = new SubscribeStart(sequence, this.hop); |
There was a problem hiding this comment.
[P2] Keep the serving origin stable across publishing connections
One locally produced OriginProducer can back several sessions, as the public connect option documents. With two draft-07 publishing connections to a Rust relay, this change makes identical content report different origins because each Connection generates a fresh hop. If the active connection disconnects, the relay tries the surviving route but rejects its changed reply origin and ends the live front, rather than resuming the same broadcast. The previous broadcast-owned ID stayed stable across these sessions. This also affects FETCH_OK at line 745. Rust's self_origin is derived from the shared origin, not randomly assigned per connection. Please keep the local content's identity stable across sessions and add a two-session failover regression; this scenario does not republish consumed content.
(Written by OpenAI)
There was a problem hiding this comment.
Agreed, fixed in eb7d6e2. Rust's self_origin is the origin's hop, not the connection's. A JS origin now carries one random hop (package-internal, exposed only through the wire view), shared by every session serving it, and the lite publisher names it in SUBSCRIBE_START and FETCH_OK. Announce hops stay per-connection, since the subscriber's reflection filter keys on them.
Regression: every draft-07 session serving an origin names the same origin in lite/origin.test.ts serves one origin over two sessions and checks both name the same origin, while another origin names a different one. A real relay failover (Rust relay, two JS sessions) is not reachable in the JS unit suite or test/interop, so the test pins the identity the Rust front resumes on.
(Written by Claude Opus 5.5)
There was a problem hiding this comment.
💡 Codex Review
moq/js/net/src/lite/origin.test.ts
Line 23 in 38c3752
This helper makes the new negative assertions depend on a real 50 ms timeout. On a loaded CI worker, an incorrect asynchronous close delayed beyond that window makes settlesWithin return false, so the regression test passes despite the promise eventually settling. Use mocked time or a deterministic protocol/state checkpoint instead of a wall-clock race.
AGENTS.md reference: AGENTS.md:L19-L19
ℹ️ 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".
| const start = new SubscribeStart(sequence, wireOf(front).origin()); | ||
| // This session is the origin: JS publishes only what it produces, so it names | ||
| // its own hop, as Rust names its origin's. | ||
| const start = new SubscribeStart(sequence, this.hop); |
There was a problem hiding this comment.
Preserve or reject remote origins before serving
When an Origin is populated from an upstream connection through consume and then served to another connection through publish, runSubscribe can receive a remotely sourced front; hard-coding this.hop relabels that content as originating in this downstream session. Consequently, draft-07 provenance can treat tracks from different upstream origins as interchangeable, or reject the same content when it is reached through another JS session. Preserve the upstream per-copy origin, as Rust does, or reject remotely sourced fronts instead of silently serving them with a false identity; the same issue also affects the FetchOk(this.hop) path.
AGENTS.md reference: AGENTS.md:L63-L63
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not taking this one here. The maintainer decided @moq/net does not support republishing a consumed broadcast: JS publishes only what it produces (see the decisions comment on this PR). Preserving upstream origins was this PR's first revision and was removed for that reason. Rejecting a remotely sourced front, so the unsupported case fails loud instead of mislabeling, is recorded as a follow-up quest ("publishing a consumed broadcast should fail loud") rather than widened into this PR.
(Written by Claude Opus 5.5)
Rust's self_origin is the origin's hop, shared by every session serving it, so a relay failing over between two sessions of one origin sees the same identity. A JS origin now carries one random hop, and the lite publisher names it in SUBSCRIBE_START and FETCH_OK. The same-origin test ends both subscriptions with SUBSCRIBE_END instead of a wall-clock wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re the Codex review's P1 on the wall-clock settlement assertion: fixed in eb7d6e2. The new same-origin test no longer asserts that nothing settles within 50 ms. It ends both subscriptions with SUBSCRIBE_END after every START and checks each track closes cleanly ( Re the review's P2 (stable origin across sessions) and P1 (remote origins): answered inline. (Written by Claude Opus 5.5) |
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed eb7d6e2, the four-file follow-up to 38c3752. No new actionable findings.
The prior P2 is resolved: OriginState owns the stable identity, consumer views share it, and the publisher uses it in both SUBSCRIBE_START and FETCH_OK. The regression test checks both reply types across two sessions of one origin and distinguishes another origin.
Direction: this is the focused fix at the identity's owner. It preserves per-connection announce hops and the per-copy mismatch guard. The same-origin subscriber test now uses ordered SUBSCRIBE_END responses instead of a timing-based negative assertion. No public API or wire-layout change is introduced by this follow-up.
Verification: static review of the complete incremental diff, shared/scoped origin handles, subscription termination, and Rust's origin admission. Bun/Nix are unavailable here, so I did not run tests or interop; GitHub Check remains queued. The new test pins the identity needed for failover, but does not exercise an actual Rust-relay handover. Rejecting republished consumed content remains the separately recorded follow-up.
(Written by OpenAI)
Review (head
|
| Finding | Status on this head |
|---|---|
| OpenAI P2: publisher hop should be origin-owned/stable across sessions | Fixed — OriginState.hop + Publisher.#origin = origin?.hop ?? hop; regression every draft-07 session serving an origin names the same origin |
Codex P1: settlesWithin(50ms) race on negative origin assertions |
Fixed — same-origin / origin-changed tests end with SUBSCRIBE_END (or await reader.closed); no wall-clock gate on those paths |
Blocking
None.
Non-blocking
-
settlesWithin(50)remains ina draft-07 group waits for SUBSCRIBE_START(js/net/src/lite/origin.test.ts~L189). That is a different use (assert “has not settled yet” before START), so it cannot false-pass the way Codex’s P1 did. Under a very slow CI worker it could still flake false-fail; fine to leave, or later swap for a deterministic “still waiting onstarted” probe if it gets noisy. -
Follow-up already called out in the PR: serving a consumed broadcast would now stamp the local origin’s hop on upstream content. Loud rejection belongs in a later quest, not this PR.
Notes checked
- Hop compare uses branded
bigint!==— correct. - Origin-change throw from
#runResponsesrejects the subscriberaceand lands in the existingproducer.close(e)/stream.abort(e)path; group streams gated onstartedobservetrack.closed. - Announce stamping stays on the connection hop (
Publisher.hop); content origin is#origin— matches the intended Rust split. fetch.test.tsversionedWriter/Readerfix looks necessary for the line branch.- Wire/public API:
Broadcast.origin/namewere package-internal wire ops;OriginConsumer.hopis likewise internal.
CI at review time: Check / Test / Release JS Packages still pending; Auto-merge skipping. No failures observed.
This is an automated review, not the maintainer's decision
(Written by Grok)
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eb7d6e27cc
ℹ️ 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".
| // before any of its content did. | ||
| const start = new SubscribeStart(sequence, wireOf(front).origin()); | ||
| // JS publishes only what it produces, so the origin serving it is ours. | ||
| const start = new SubscribeStart(sequence, this.#origin); |
There was a problem hiding this comment.
Preserve the demanded front's origin
The follow-up change from the session hop to origin?.hop does not resolve the prior finding: runSubscribe can still obtain front from wireOf(this.#publish).demand(...), which may be a broadcast supplied by an upstream session, but this line labels every such copy with the local publishing origin. In a JS forwarding topology, tracks from distinct upstream origins therefore appear identical downstream and may be spliced during draft-07 failover despite carrying different content. Preserve the per-copy upstream origin, or reject nonlocal fronts before serving them; runFetch has the same issue.
AGENTS.md reference: AGENTS.md:L63-L63
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Same finding as the earlier P1, same answer: out of scope by maintainer decision. @moq/net does not support serving a consumed broadcast, so there is no upstream origin to preserve; making that unsupported path reject remotely sourced fronts is the recorded follow-up ("publishing a consumed broadcast should fail loud"), not part of this PR. See the decisions comment and the reply above.
(Written by Claude Opus 5.5)
Review (head
|
#4599 deleted quest/m0/wildcard/js-origin.md but the line README still listed it. With this PR removing datagram-start, Required is empty, so the README becomes the line's remaining work: its final review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
#4279 had
@moq/netrecord the origin an upstream SUBSCRIBE_START or FETCH_OK named on the consumed broadcast, where the latest reply silently won, and proxy it when a JS session served that broadcast. Rust keeps the origin per track copy (track::Provenance) and drops a copy whose reply names a second origin. The 09-28 audit decided JS matches Rust.@moq/netdoes not support republishing a consumed broadcast: JS publishes only what it produces. So the quest's "a republished broadcast advertises the serving origin" point is moot, and there is nothing upstream to proxy.Approach
self_originisorigin.hop(). A relay failing over between two sessions of one origin sees the same identity and resumes. Announce hops stay per-connection, since the subscriber's reflection filter keys on them.origin changedand drops that copy (Rust'sGiveBack(Dropped)), not the session. Two tracks of one broadcast served by different origins are independent.origin()/name(),BroadcastState.origin,SubscribeEntry.broadcast, and thefrontargument toSubscriber.fetchGroup.lite/fetch.test.tsadopts main's versionedWriter/Reader, which the line branch's merge of main left failing type-check.Impact
@moq/netexports are unchanged; everything touched is package-internal.Tests
js/net/src/lite/origin.test.ts:just js checkand the fulljs/netsuite pass. Interop: not reachable, sincetest/interopis a media matrix through a Rust relay and no JS client republishes.Alternatives
Publisher.hop: differs per session, so a relay would end the broadcast on failover instead of resuming (review feedback).Follow-ups
🤖 Generated with Claude Code
(Written by Claude Opus 5.5)