Repository navigation
Conversation
Quest-Claim: a84bc9b1332f4285
js/net now reads and writes lite-07-wip's route layer as Rust does: - The codec carries ROUTE_START/UPDATE/HOLD/END, ANNOUNCE_FLOOR, and the subscriber's ROUTE_REQUEST and ROUTE_END echo. Claims name a Route ID in place of a hop chain, and every TRACK, TRACKS, SUBSCRIBE, FETCH, and DATAGRAMS request names an origin Node ID (0 lets the publisher choose). - A publisher answers from its origin's own route: one Node ID per origin, claims in batches closed by a floor, ROUTE_END after the last withdrawal, and a restart only after the echo. A browser forwards nothing. - A subscriber applies each route's batch at its floor, reports the best claim per path with the route's metric, availability, and node, echoes every ROUTE_END, and pins each consume's requests to the node it selected. - A detached request follows its instance's cheapest route, as Rust does. - Rust and JS share route-message cases in fixtures/lite.json, the announce goldens match lite::compress, and the JS lite-07 interop lanes, datagram tracks, and nightly drain run again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lite-07 relay forwards a peer's claims to its other peers only as a proxy, as max_age's relay already does, so the restored JS datagram-tracks leg through the relay found nothing to subscribe to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… inline - The publisher applies each announce reply as it decodes it rather than queueing it, so a peer cannot grow a queue while our writes stall. - The subscriber files every route's claim in the consume cache under its own owner, so a consume ranks each node's claim for its own path, epochs included, and spreads a prefix's children by rendezvous hash. - A consume names the node now serving its instance, following failover. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ws its own routes - BroadcastCache ranks the newest stream first on a tie at its own prefix, as refresh(), the origin, and Rust do, so a restart's consumers open the instance it reported. - A consume names the node of its own instance's best route even once another instance wins the path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Design change: the maintainer picked B (rethink) at the round-3 checkpoint. The announcement and the consume cache no longer rank separately. (Written by Claude Opus 5.5 with medium effort) |
- bestCandidate ports best_route's pick: epochs, route_order (the node, instance, and chain hashes), then the newest claim. The announcements, BroadcastCache.instance and BroadcastCache.node all rank through it. - A claim is the newest when it starts or restarts, and keeps its place through a re-price, as a Rust entry keeps its id. The announce stream no longer reorders on every floor, so it and the consumes agree. - lite07_ties_match_js pins the node hash and both tie rules in Rust, and the JS tests assert the same picks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts origin - No route starts until every ended one is echoed, so a peer that keeps rotating our identity (a ROUTE_REQUEST for the last seqno) and withholds its echoes holds one ended route, not one per rotation. - A closed publishing origin ends its claims and route, then the answer finishes, as the pre-lite-07 path does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Superseding the B note above: the maintainer then chose to break ties exactly as rs/moq-net does.
(Written by Claude Opus 5.5 with high effort) |
|
Design change: the maintainer replaced the B pick with one rule. js/net breaks route ties exactly as rs/moq-net does, in every selector. (Written by Claude Opus 5.5 with high effort) |
…d routes as Rust - A request through a route the origin received consumes the session pinned to that route's instance (Established.consume takes an optional instance), so another instance's claim at the prefix, ranked apart, is never served under this one's identity. - compareRouteCandidates keys each candidate on its own, a lite-07 claim by its node and any other by its instance and chain, as route_order does, instead of keying a lite-06 route at zero beside a lite-07 one. - The origin's announcement order counts a lite-07 claim as one hop, as requests already did (routeHops). - lite07_ties_match_js pins the mixed pick in Rust. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…re-pins - routes() takes an internal claims option: a lite-07 stream then reports each origin node's claim at a path as its own route rather than only the best one, as Rust's routed.rs forwards each claim. The bridge asks for it and keys its routes by path and node, so the origin ranks every candidate, spreads a prefix's children by node, and resolves a request pinned to a claim that does not win. announced() keeps one route per path. - A route handed to a report of another instance restarts, and drive reads the handle's instance per request, so a handover never serves one instance under another's identity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing/routes/js # Conflicts: # js/net/src/origin.ts
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed 5c8f391. No actionable defects found in the reviewed route-layer implementation and integration changes.
Direction: the wire codecs align with the Rust route messages, while publisher end/echo handling bounds retained routes. Subscriber batches commit at floors, the consume cache retains every node's claim, and the origin bridge now pins requests to the received instance. Shared ranking and the added tie/reprice, fallback, and pinned-instance regressions address consistency between announcement and consumption. Restored interop lanes exercise the newly supported lite-07 paths.
Scope caveat: cross-stream certifying-floor reconciliation remains explicitly unimplemented; this review does not establish multi-upstream consistency.
Verification limits: GitHub-only static review of the changed source, fixtures, tests, and workflow wiring, with Rust codec comparison. No builds, tests, browser/media runs, or performance measurements executed. Author-reported checks were not independently reproduced; the head's Check workflow was still in progress when inspected.
|
No actionable comments were generated in the recent review. WalkthroughThe JavaScript network stack now supports Lite 07 routed announcements, including route starts, updates, holds, ends, floors, and replies. Requests carry origin-node information, and route selection supports node-aware ranking and instance pins. Publisher and subscriber flows handle claims, route changes, and failover. The changes also update Rust fixtures and tests, enable Lite 07 interop cases, and revise related workflow and quest documentation. Merge Risk:
|
There was a problem hiding this comment.
Important
The new lite-07 publisher only re-floors on a claim change, so a grant change that flips whether the prefix may be certified never reaches the peer. Rust re-floors every route on every permit change, and a Rust peer acts on the difference (it drops claims no floor covers when it adopts a view).
Reviewed changes
- Route codec — ROUTE_START/UPDATE/HOLD/END, ANNOUNCE_FLOOR, and the subscriber's ROUTE_REQUEST/ROUTE_END echo, with lite-07 claims naming a Route ID in place of a hop chain and every request naming a Node ID.
- Routed publisher —
#runRoutedadvertises the origin's claims under one route, closes each batch with a floor, restarts only after every echo, and refuses requests naming another node. - Routed subscriber —
#receiveRoutedapplies each route's claims whole at its floor, reports the best claim per path (or per node for forwarding), takes a ROUTE_END's claimsoffline, and echoes it. - Selection parity —
bestCandidate,rendezvousHash,routeKey, androuteHopsport Rust'sbest_route/route_order, including the newest claim winning a full tie while a re-price keeps its place. - Consume and forwarding — the cache selects and names the node of an instance's best route;
forwardAnnouncedkeys routes by prefix plus node and pins forwarded requests to the route's instance. - Origin —
pickandfailovermerge intofollow, so a detached or pinned request follows its instance's cheapest route rather than staying on the first it resolved. - Interop and tests — 13 lite-07 route fixture cases replayed by both sides, Rust's
lite07_ties_match_jsand the shared goldens, and every JS lite-07 lane (interop, sockets, max-age, relay-fill, datagram-tracks, nightly drain, media replacement) restored.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
| await write({ status: "update", id: prev.id, cost }); | ||
| } | ||
| } | ||
| if (!route || !changed) return; |
There was a problem hiding this comment.
A grant change only reaches the peer through a claim change: sync() returns here before the floor whenever latest is unchanged, even when certifies flipped at line 874. A token offered (auth_session.ts offer) or withdrawn (withdraw) on the live session therefore leaves the route's claims uncertified, or leaves an old certification standing, for the rest of the stream, and a Rust peer acts on exactly that difference.
Technical details
# A floor is only written when a claim message moved
## Affected sites
- `js/net/src/lite/publisher.ts:874` — `certifies` is read from `certified()`, which depends on the live grant (`#grant.peek()`); the grant is subscribed at `publisher.ts:964-966`, so a change does run `sync()`.
- `js/net/src/lite/publisher.ts:918` — `if (!route || !changed) return;` skips the floor whenever `visible()` produced the same claim set, which is exactly what a grant change that keeps every currently shown claim does.
- `js/net/src/lite/publisher.ts:919` — the floor's revision is the only thing that tells the peer the prefix is certified.
## Scenario
1. A lite-07 announce stream is open with `prefix = a`, the grant covers only `a/x/**`, and the origin's only claim is `a/x`. `sync()` sends `active a/x` and `floor(id, revision: undefined)`, so the peer holds the claim uncertified.
2. A token covering `a/**` is offered on the session (or, symmetrically, the token covering `a/**` is withdrawn from a grant that already showed the same claims). `#grant` pokes, `sync()` runs with an identical `latest` but `certifies` flipped.
3. `changed` stays `false`, so no floor is written. The claims stay uncertified for the life of the stream, and a Rust peer's `View::restrict`/`restrict_all` keeps dropping them (`rs/moq-net/src/route/mod.rs:1285` retains only claims a kept floor covers), so the browser's broadcasts are never adopted into that node's route table.
Rust does not have this gap: a permit change calls `refilter`, which re-floors every route unconditionally, and `floor()` re-derives `certified` on each call and sends `None` when it no longer holds (`rs/moq-net/src/lite/publisher/routed.rs:258-264`, `531-548`, `640-651`).
## Required outcome
- A floor is written whenever the route's certification changes, even when no `active`/`update`/`restart`/`endedId` was written in that pass.
- A certifying revision is only ever the latest floor's value while `certified()` still holds for the prefix.
## Suggested approach
- Track the certification of the last floor written for the current route (reset it when a route starts), and change the condition at line 918 to write a floor when `changed` or that value differs from `certifies`.
- A test in `js/net/src/lite/publisher.test.ts` on the `routedAnswer` harness: open the stream under a grant that covers one claim but not the prefix's subtree, then set a grant that covers the subtree and expect `floor(id, revision)` without any claim message before it; the reverse shrink expects `floor(id)` with no revision.|
Heads-up from moq-dev/moq#8458, which follows the maintainer's 2026-11-10 decision: lite-07 ROUTE_START and ROUTE_UPDATE replace (Written by Claude Opus 5.5 with high) |

js/netnow speaks lite-07's route layer as Rust does. It reads and writes ROUTE messages and ANNOUNCE_FLOOR, applies each route's claims a batch at a time, echoes every ROUTE_END, and pins each request to the origin node it selected. A browser stays non-transit: it advertises its own origin's claims under one route of its own and forwards nothing.Quest: deletes quest/m2/cluster-routing/routes/js.md and its links in the routes epic README and in
acceptance.md, whoseRequiredsection it emptied.Decisions
byPathonly for a new claim or restart; C, merge as is).bestCandidateinjs/net/src/internal.tsportsbest_route. Only an instance's highest epoch is eligible. Routes rank byroute_order, throughcompareRouteCandidates: the rendezvous hash of each lite-07 claim's origin node, or the instance and chain hashes. The newest claim wins a full tie, as Rust's finalReverse(entry.id).Link::Local) at its own path, which is every route a browser holds. There the newest claim decides. A claim counts as new when it starts or restarts, and a re-price keeps its place, as a Rust entry keeps its id throughupdate.BroadcastCache.instance, andBroadcastCache.nodeall rank through it.lite07_ties_match_jsinrs/moq-netpins the node hash and both tie rules. "a lite-07 tie at a claim's path goes to the newest claim, a re-price keeping its place" checks that the announcement and the consume agree, and fails ona817f15db1. "lite-07 children of a prefix two origins claim spread by node" asserts Rust's pinned hashes and pick.Changes
Codec (
lite/announce.ts,lite/instance.ts, the request messages)version.tsrenameshasNodeIdtohasRoutes. On lite-07, SETUP's 0x5 parameter carries the publishing origin's Node ID.Publisher (
lite/publisher.ts, newroute.ts)Own): a random 63-bit Node ID shared by its sessions, a seqno, and a revision.publisher/routed.rs:Unroutable.Subscriber (
lite/subscriber.ts)#receiveRoutedtracks each route and its claims.compareRouteCandidates, with the newest winning a tie.availableis false while the route is held.update, flippingRouteInfo.available.offline(notend), unless another route still serves the path. The subscriber then echoes it.node, and its SUBSCRIBE, TRACK, TRACKS, FETCH, and DATAGRAMS requests name it.Origin (
origin.ts): applies the 2026-10-27 maintainer decision on moq-dev/moq#6947. A detached or pinned request now follows its instance's cheapest route, as Rust's does, instead of staying on the route it first resolved while that route still serves.pickandfailoverare merged into onefollow.Interop
rs/moq-net/fixtures/lite.jsongains 13 lite-07 route cases, replayed by bothcorpus.rsandcorpus.test.ts, with a newannounce_replymessage kind.announce.test.tsdecodes Rust'slite::compressGOLDEN, and its encoder writesJS_LITERALbyte for byte. Rust'scompress.rscomments now point at that test.JS_LITE07_SKIP,js_lite07, and theneeds_jsversion guard are removed frominterop.shandsocket.sh. Native JS prints its grant again.tracks_interopis back injust test interop.max_age_relay_javascriptandrelay_fill_javascriptare back.datagram_tracks_javascripthas its original ignore reason, and its test-inventory entries are gone.Datagram tracksinterop workflow step andtest drainin the nightly matrix are back.replacementmedia quarantine is removed. The Firefox audio-tone quarantine stays.bestCandidate; see Decisions. The announce stream moves a claim to the newest only when it starts or restarts, as the consume cache does.compareRouteCandidateskeys each candidate on its own, asroute_orderdoes: a lite-07 claim by the rendezvous hash of its node, any other by its instance and chain hashes. The origin's announcement order counts a lite-07 claim as one hop (routeHops), as requests already did.routes()takes an internalclaimsoption (RouteOptions). With it, a lite-07 stream reports each origin node's claim at a path as its own route, as Rust'srouted.rsforwards each claim into its origin.forwardAnnouncedasks for it and keys its routes by path and node. The origin then spreads a prefix's children by node and resolves a request pinned to a claim that does not win. The publicannounced()still reports one route per path.Established.consume(path, instance),BroadcastCache.pinned). A handover to a report of another instance restarts the route. Either way, one instance's content is never served under another's identity.datagram_tracksrelay is now a proxy, asmax_age's became in moq-dev/moq#8023.Choices made without asking
Unroutableand is retried.hopson a lite-07 route is empty, withnodeset.compareRouteCandidatescounts such a route as one hop and spreads ties by the rendezvous hash of its node, asroute_orderdoes.offline, notend. The publisher sends ROUTE_END, as Rust's does, and ROUTE_END withdraws nothing.close.test.tsnow expectsofflinefor both protocols.active), since lite-07 always takes the routed path.Caused by
Impact
moq-lite-07-wiponly, which is unpublished.js/netmoves from the pre-route lite-07 wire to the route layer Rust already speaks. Published versions are unchanged.@moq/net's exported surface (api/index.api.md) is unchanged. The lite codec types andOwnare internal.announce_replycorpus kind, restores interop legs, and updates comments.Checks
Run locally on
d448e32e79unless noted:just checkpasses: 12936 Rust tests and the js/net suite. The pre-existingcheck cfailure did not show up.aa4a00df0banda817f15db1, after review rounds 1 and 2:just checkpasses again, with 3230 js/net tests.just fixleaves nothing to change.just test interoppass, including the restoredlite_tracks_interop.interop.sh --publishers rust,js --subscribers rust,js,js-native-node,js-native-bunpasses all 79 cases. That covers native JS on lite-07: claims, track replacement, sockets, compress, the compressed catalog with its lite-07 reader, and the drop and tail legs.finish(already quarantined).just test max-age,relay-fill, anddatagram-trackspass, each now including its JS lite-07 leg.datagram-tracksneeded one fix: its relay is now a proxy, asmax_age's already was. A lite-07 relay forwards a peer's claims only as a proxy.interop --all. They speak through moq-ffi, which this PR does not touch.just test drainand themediareplacement case. CI's nightly and media jobs cover them.5c8f391f24, after the tie decision, the review fixes, and mergingmain:just checkpasses (12938 Rust tests, 3240 js/net tests), and so doesquest check --base origin/main. Each new test fails without its fix.Follow-ups
js/net. Recommendation: plan it alongside P2P or multi-upstream browsers, when one origin's claims first reach a tab over several sessions. Until then it adds per-node withdrawal state for no observable gain.Models
Size: L
Implementer: Claude Code, claude-opus-5-5, high effort
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)
(Written by Claude Opus 5.5 with high effort)
🤖 Generated with Claude Code
Review
Rewritten by
quest reviewafter each round.01a1c6ba-c521-72f0-be1d-303ad4312ee3, round 2The merge preserves both earlier fixes, and source inspection found no new actionable regressions; builds and tests were not run as instructed.