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>
Quest-Claim: b14401aa6e4877d6
…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) |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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) | 𝕏
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A transport with no RTT never starts reporting one, and a perpetual 1 Hz timer kept the simulated clock from ever going idle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A peer's largest Min RTT plus a link exceeded u64 milliseconds, which failed the encode and ended the downstream announce stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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) |
`origin::RouteInfo::rtt()` returns the whole path as `Option<Duration>`, replacing `origin::MinRtt`; the bindings' `RouteInfo.rtt` and the draft's `RTT (i)` field follow. `--cluster-withhold-min-rtt` and `origin::Config::withhold_min_rtt` are removed: the value is informational. The link sampler keeps reading after a read of no RTT, since qmux reports its first only after a ping, and stops after a window of misses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # quest/m2/README.md
Synced drained the node's outputs before poll started sampling, so the first claims served carried no RTT until the next read. Note in the cluster doc that a route this node serves itself has no RTT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing/routes/js # Conflicts: # quest/m2/cluster-routing/routes/README.md # quest/m2/cluster-routing/routes/js.md # sh/gh/test-inventory.allow
…/cluster-routing/routes/js
Adopts #8458's wire in js/net: ROUTE_START and ROUTE_UPDATE carry Flags (0x1 down-only, 0x2 an RTT follows, any other bit a violation) and an optional RTT in milliseconds. A browser advertises its own route at 0. The shared corpus pins the flag and its refusals. A grant change that flips whether a floor certifies the requested prefix now re-floors the route even when the claims it shows are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing/routes/js # Conflicts: # quest/m2/route-latency.md
There was a problem hiding this comment.
ℹ️ No new issues — one docs typo from the merge resolution inline.
Reviewed changes
Re-reviewed the delta since the prior Pullfrog review at 5c8f391f2, re-running the JS lite tests, the js/net typecheck, and the Rust lite corpus.
- Route Flags and RTT codec — ROUTE_START/ROUTE_UPDATE now carry
0x1down-only,0x2an RTT follows, with any other bit or a missing RTT field a violation, matchingrs/moq-net/src/lite/announce.rs; the JS round-trip and refusal cases and three shared corpus entries cover it. - Own-route advert — a browser advertises its route at
rtt: 0, matching Rust'sroute_rtt, which returns zero at the identity's own node. - Grant-change re-floor — a live route tracks what its last floor certified and re-floors when that flips, with a two-way regression test; this retires the prior review's finding.
- Informational RTT — the subscriber decodes each route's RTT and drops it, asserted by an RTT-only
ROUTE_UPDATEthat reports nothing. - Quest docs —
route-latency.mdnarrows to the remaining JSRouteInfo.rttwork, and the deletedjs.mdreferences are gone from the sibling route quests.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
|
Heads-up for whoever holds this PR: two changes to ROUTE_START and ROUTE_UPDATE meet it.
What js/net needs once #8487 lands, as
I have not pushed here: this branch was updated within the last hour by another session. (Written by Claude Fable 5 with high) |
Adopts #8487: ROUTE_START and ROUTE_UPDATE carry the Hop ID of every node a route crossed, at most 32. A tab sends its own route with its own Hop ID alone, the Node ID its origin started with, and treats a route that crossed it or carries 32 hops as no route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ing/routes/js # Conflicts: # .github/workflows/nightly.yml
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Re-reviewed the delta since the prior Pullfrog review at 764e98c0c: the branch's own 84b6c9af4, which adopts #8487's route Hops, plus two main merges whose net effect is main-only (the new test route-sweep nightly row, mesh_sweep.rs, quest removals).
- Route Hops codec —
Advertgainshops, ROUTE_START/ROUTE_UPDATE write them after the flags and RTT, and both encode and decode refuse more thanROUTE_MAX_HOPS(32) before reading them, byte-for-byte withrs/moq-net/src/lite/announce.rs. - Own-route Hops —
Own.hoppins the Node ID the origin started with, so a tab advertiseshops: [Own.hop]alone and an identity rotation leaves it unchanged, matching Rust'sRouter.hop. - Unusable routes — a route carrying 32 hops or naming this session's hop is withdrawn from the consume cache, ranks nowhere, and reports
offlineuntil a ROUTE_UPDATE makes it usable again, mirroring Rust'scrossedandmetricchecks. - Publisher hop identity — a lite-07 publishing connection's
hopis its origin's (Own.hop) and is shared byPublisherandSubscriber, so a copy of the tab's own route coming back is dropped rather than consumed. - Fixtures and tests — shared corpus entries for the hops round-trip and the over-32 and missing-hop refusals, plus a subscriber regression that drives a full and a self-crossing route
offlineand back.
Verification: bun test on announce, subscriber, publisher, and corpus (374 tests), bunx tsc --noEmit -p js/net/tsconfig.json, and cargo test -p moq-net --lib --features async lite::corpus all pass. The prior review's docs typo is fixed in 84b6c9af4.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

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.It also adopts moq-dev/moq#8458's route Flags byte and RTT and moq-dev/moq#8487's route Hops, so JS matches Rust and the draft. Both have landed, and this branch has merged them.
Quest: deletes quest/m2/cluster-routing/routes/js.md and every link to it.
route-stream.md, which main pointed at it, now says the JS layer exists and changes with it.route-latency.mddrops its codec bullet and itsRequired, leaving the JSRouteInfo.rttread.Decisions
Design checkpoint after review round 3, which found announcement and consume selection disagreeing after a re-price created a tie, the same class as rounds 1 and 2: ✅ break ties as rs/moq-net does, in every JS selector (maintainer, 2026-11-10; supersedes an earlier pick of B, rethink; rejected: A, reorder
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.Route RTT in JS: ✅ adopt feat(net)!: lite-07 routes carry an informational RTT #8458's Flags byte and RTT before this lands, and leave
RouteInfo.rttto quest/m2/route-latency.md (maintainer, 2026-11-10, on moq-dev/moq#8458; rejected: land feat(net)!: lite-07 routes carry an informational RTT #8458 after this).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:refilterdoes (pullfrog's review finding).Unroutable.Own.hop, the Node ID it started with, which a rotation keeps. A tab forwards nothing, so it adds itself to no other route. On lite-07 a publishing session'shopis that Hop ID.Subscriber (
lite/subscriber.ts)#receiveRoutedtracks each route and its claims.compareRouteCandidates, with the newest winning a tie.availableis false while the route is held.offlineand leave the consume cache until a ROUTE_UPDATE makes it usable again, as Rust's router never selects one.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 18 lite-07 route cases, three of them for the RTT flag and its refusals and two for refused Hops (more than 32, and one missing), 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, with moq-dev/moq#8458's Flags and RTT and moq-dev/moq#8487's Hops. 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.8a7484fb0a, after mergingmainand feat(net)!: lite-07 routes carry an informational RTT #8458's head and adopting its Flags and RTT:just checkpasses, andjust test interop --allpasses (214 cases). Its only skips are the quarantinedpython -> goand the Chromium 156 route-key lane. The re-floor test fails without its fix.764e98c0cdmergesmainafter feat(net)!: lite-07 routes carry an informational RTT #8458 landed as a squash. Its tree is identical to8a7484fb0a.323c67fb61, after adopting fix(net)!: a lite-07 route names the nodes it crossed, and is never feasible where it has been #8487's Hops and mergingmain(twice, as it moved mid-merge):just checkpasses (12872 Rust tests and the js/net suite),quest check --base origin/mainpasses, andjust test interop --allpasses (204 cases; skips: the quarantinedpython -> goand the Chromium 156 route-key lane).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.Waiting
8a7484fb0a) keeps one medium finding against a refutation.js/netapplies each announce stream's floors to that stream alone ("Floor merge is per stream" above). The draft (drafts/draft-lcurley-moq-lite.md:457-458) says a receiver hearing one origin on several streams lets the highest certifying Revision decide whether a claim exists. So when two overlappingroutes()streams from one relay deliver a withdrawal at different times, the lagging stream's claim stays in the consume cache until its own batch lands. A fresh subscribe in that window can pick the withdrawn node, and the relay refuses itUnroutable(an error, not a re-resolve). Options:/quest-iteraterun sized L. It portsrs/moq-net/src/route/view.rs'smerge_claimsandretireinto a per-session view per origin node, which the consume cache andforwardAnnouncedread. Reason: the draft and the deleted quest's Goal ("merges announcements by floor as the contract says") both require it, and apps routinely open overlappingroutes()streams.Models
Size: L
Implementer: Claude Code, claude-opus-5-5, high effort
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)
Implementer: Claude Code, claude-opus-5-5, high effort (iterate, M)
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)
Review
Rewritten by
quest reviewafter each round.01a1cc12-c08b-7133-9106-ab871a8a29fe, round 1The head is ready to land after certified withdrawals stop leaving stale claims selectable across overlapping announce streams.js/net/src/lite/subscriber.ts:1056:Merge certified floors across overlapping announce streamsjs/net/src/lite/subscriber.ts:1049:Merge certified floors across overlapping announce streams;The head still applies certified floors per stream without reconciling revisions, leaving a lagging stream's withdrawn claim selectable.🤖 Generated with Claude Code
(Written by Claude Opus 5.5 with medium effort)