Repository navigation
feat(net)!: a job's exact route continues its claim's instance - #8063
Conversation
Quest-Claim: e7a08a7c96080d03
A floor is now one marker per route across the stream's interest. The router presents its per-region view onto it: claims inside keep their path, the most specific covering claim moves onto the interest (moved, not ended, when withdrawn), and the floor certifies only when the view knows the whole interest at one revision. Claims a stream carries uncertified stay in its heard view and show where no certified floor proves them absent; they go offline, never END, when their stream drops them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The origin keeps a route layer node its lite-07 sessions will share, created on first use with a driver task that announces every route the origin serves itself. A route the route layer selected names its origin node, which a reader can filter on and which ranks routes by the rendezvous hash of the path and that node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every relay now takes a random Hop ID on each start: `--cluster-id`, `MOQ_CLUSTER_ID`, and `[cluster] id` are gone and refused as unknown settings, matching the route layer's per-process node identity. A cluster link costs at least 1, the price of an unpriced link. A peer priced at 0 (`?cost=0`, or an object `cost` or `egress` of 0) is a configuration error, as is a LAN-advertised `cluster.node` whose cost is 0 or malformed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ANNOUNCE_START and ANNOUNCE_TAKEOVER carry the Route ID, ANNOUNCE_UPDATE only the Announce Cost, and ANNOUNCE_RESTART no hops; hop bases are gone. ANNOUNCE_OK has no body and SETUP no Hop parameter on lite-07, a Cost of 0 is refused, and SUBSCRIBE, FETCH, TRACK, TRACKS, and DATAGRAMS carry the Node ID. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each lite-07 announce stream registers with the origin's route node: the subscriber feeds it the peer's routes and claim batches (closed by ANNOUNCE_FLOOR) and serves the claims the node hands it, minting sources whose requests name the claim's Node ID; the publisher answers from the node, filtering hidden, out-of-scope, and ungranted claims (a floor then certifies nothing). Requests resolve through the Node ID they name. A scoped peer's identities are qualified under its proven route key. A per-connection proxy preference decides whether a peer is sent routes learned from other peers: relays on, the library default off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A claim is served through every stream with a usable feasible route to its origin node, so the origin holds the standby and fails over at once when a session drops, as it did with hop chains. Own claims reach the node before the first stream answers, and feeding them asks no peer. A re-filed stream withdraws what it no longer serves, a covering claim is answered like a covering reader sees it, and a route that entered here for exactly the requested path ranks newest-first. The mesh counterexamples now retract once everywhere. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A front ending released the copy of every track nobody read, including one that had already finished. A replaced run whose track finished before the restart reached the subscriber therefore lost its cached groups, and a recorder fetching them failed with the segment unavailable. Main hits the same failure once the old run's end lands first; lite-07's routed restart just arrives a little later. An ended copy subscribes nothing, so keeping it costs only what its readers already hold. moq-hls's restart suite gains a case that lets the old run's end settle before the new instance announces, so the ordering no longer decides it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A client took the grant its relay answered with as a scope on the relay, so it qualified the relay's identities and refused every one the relay had already qualified: two clients behind a relay never saw each other's endpoints. A connection's identities are qualified when its publish grant is scoped, and that grant is the one this side issued, the permit's limit. What the peer granted this side limits only what this side does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A relay built from the library now opts into proxying, as the relay binary does, or a lite-07 peer is sent only its own claims: moq-mux's links, moq-stats' and moq-tokio's relays, and the goaway suite's fake upstream. Lite-07 carries no hop chain, so the hop-limit tests run on the older wires only, and tiered_regions checks reachability and core failover rather than which core a path's chain names. The first hop picks a pool's origin by a hash every node agrees on, so prefix_pool checks that the downstream relay agrees with the upstream one and that a joining worker takes only paths it wins. With lite-07's takeover gone until quest/m2/announce-takeover.md, claim_restart wakes the worker before subscribing, as the older wires do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The lite-07 session drives the route layer now, so its blanket dead_code allowance goes: test-only queries are gated on test, and builds without async allow only what the async session alone drives. ROUTE_REQUEST and the announce replies lose their allowances too. The routed streams log each announce and restart as the older wire does, and `Client::with_cost` documents that lite-07 refuses zero. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A relay translating a moq-lite-07 claim for a moq-lite-06 peer starts the chain with the claiming node's route identity hop. When that chain came back to the claiming node over lite-06, nothing in it named the node's own origin hop, so it was filed as one of the node's own claims again and kept the broadcast circling after its publisher left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…arries on One publisher reaches a relay through two neighbors and the preferred one goes away. Wires that mint an instance per announcement restart the route onto the standby's; lite-07 carries the publisher's instance and already serves the claim through both, so it continues with nothing announced. 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/session
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ontract # Conflicts: # rs/moq-net/MIGRATE.md # rs/moq-net/src/route/mod.rs
A route-layer claim records the claim it took over and the instance that one served. The origin's own takeovers feed it, a relay keeps it from the ANNOUNCE_TAKEOVER it received, and an answering stream sends the claim as a takeover of its predecessor when the peer held that predecessor, under the same route, before the batch, and every claim it holds at that path serves the same instance. The receiver forwards the claim with its predecessor's route, and in place keeps the predecessor's sources. The legacy publisher and subscriber takeover paths were unreachable once lite-07 routed, so they go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
js/net still speaks lite-07 from before the route layer, so the in-tree lanes running it on lite-07-wip fail against this checkout's relay. Native JS dials its defaults in the interop matrix, and every lane that would run it on lite-07 records a skip naming quest/m2/cluster-routing/routes/js.md, which restores them. The same goes for lite_tracks_interop and the lite-07 legs of the max-age and relay-fill JS suites. The routes epic records the maintainer's 2026-11-06 picks: ANNOUNCE_TAKEOVER ported onto Route IDs, and a route sent on a stream only while it meets the stream's interest. session.md keeps the scoping work it implements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ontract # Conflicts: # rs/moq-net/MIGRATE.md
…ing, serves per claim - A lite-07 stream hands the node only the peer's claims the session's limit admits, not every claim within its regions. A limit narrower than its regions certifies nothing, so a floor never certifies a claim it left out absent. A permit change that admits a different set files the stream again, as a scope change does. - Claim changes between floors are kept as their net change to the committed claims, so what a stream holds is bounded by its claims, not by the messages it read. - The node serves a claim only through the streams that can carry it: within what this node asked the neighbor for and within its scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A stream whose reader stalls kept every route change and batch the node queued. Its outbox now holds one entry per identity: an end first, then the last start and hold, and one batch with each claim's last change and the last floor, starts before ends as the router orders them. A claim the reader was never sent leaves nothing when it ends, so what a stalled stream holds is bounded by its claims. A route started and ended unread reaches the reader as an end alone, which it acks at once since the peer never held the route. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now has the release cut write migration notes; this PR carries them in its Impact section instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only held routes The outbox now knows which routes its reader holds. A ROUTE_END of a route the reader never read, a start or a restart since its last read, cancels what is pending and is acked to the router at once, as the reader would: no peer holds it. So churning fresh identities leaves nothing behind, and an end, restart, and end of a route the reader holds reaches it as one end with one echo owed. The answer no longer acks an unread end itself; the node does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A lite-07 answer read only the requested prefix from the route node, so a mounted handle's target never reached a lite-07 reader, as it does in process and on lite-06. The answer now reads each mount beneath its prefix through a node stream of its own, at the target, and names what it presents under the mount point on the same routes. What the origin holds beneath a mount point stays hidden. A route stands while any of its streams carries it; one that stops early ends what it presented and is acked at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head 5d6e7fd against base 3ce26d1, following up on 41ecad4.
No new actionable findings in the serving-order fix. In rs/moq-net/src/route/node.rs:219–235 and 619–622, descendant jobs are queued before a claim restart, and a changed instance/epoch no longer inherits an earlier queued Serve's position. The new regression covers both drained and stalled readers without wall-clock timing.
The existing signing-identity finding remains open, not a new discovery: rs/moq-net/src/model/origin.rs:3048–3049 binds an unnamed hidden job to the claim's current instance at announce time. A signer prepared from the earlier instance() value keeps that old identity (rs/moq-crypto/src/sign/broadcast.rs:29–45); if a sibling ends before announce, the route advertises a different instance and subscribers verify against it. The ordering fix does not address this mismatch.
Direction: keep the instance-based continuity design and the local ordering fix. Resolve the documented hidden-job identity decision before landing, preferably by binding an observed identity and refusing announcement after that identity is spent, with a regression covering signer preparation followed by a sibling ending.
Verification limits: GitHub-only static follow-up focused on the ordering delta, surrounding node/queue logic and regression, plus the existing Rust signing concern. Inherited main changes were not re-reviewed wholesale. No quest commands, builds, tests, interop or browser checks run; author-reported validation was not independently reproduced. Rechecked open/non-draft state, full head/base and prior automated reviews immediately before posting.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This pass reviewed the commits pushed since the prior pullfrog review (41ecad4b4): the origin/main merge e3a8c5743 and the fix commit 5d6e7fd9a. Commits merged in from main were not treated as new work from this PR.
- A claim's restart waits behind its job's Serve —
Outbox::pushkeeps a queuedServein its slot only when the replacement names the same instance and epoch, so a claim moving to a fresh instance takes a new slot behind every pending serve instead of overwriting an earlier one. - Shown claims are served most specific first —
State::homewalks a node's claims in reverse path order, so a job's route is queued before its claim's restart when both changes reach the session in one batch. That is the real path: the origin's answer outbox merges twoownchanges into a singleROUTE_BATCH, andlimit()replays a whole route's claims in one batch. - Regression test —
route::node::tests::a_job_is_read_before_its_claims_restartdrives the job announce and the claim restart through one batch onto a stalled stream. Reverting either half of the fix fails it, so both are load-bearing and covered. - Merge with
origin/main—goaway_cluster.rsuses main'smemory::recordrather than its localnext_update, andmemory::recorddrops itsRouteEvent::Takeoverarm to match this PR removing the variant.git diff --merge-base origin/mainover those files shows only that arm, so no branch-owned assertion was lost.
cargo test -p moq-net --lib route::node passes (11 tests).
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
…r quest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A signer built from `route.instance()` before a hidden job announces named the claim's instance at that moment. If another job under the claim ended before the announce, the job announced under the claim's new instance and subscribers rejected every signed frame. Reading an unannounced job's instance now binds it. `announce()` then refuses with Unroutable once the claim has moved past it, and its requesters ask the claim again: a front treats a refusal for an instance its route has moved off as not standing, and JS requeues the request. Jobs that never read their instance still bind at announce. moq-c and moq-ffi no longer read the instance at creation, which would have bound every job. 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>
A front that joined the shared request after the claim moved asks under the new instance, so treating the refusal as stale missed it and it failed with Unroutable. Requeue the request on the claim instead, as JS does, so every requester keeps waiting, and drop the front rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kixelated
left a comment
There was a problem hiding this comment.
Automated review by review (OpenAI)
Reviewed head c76e987 against base fe00d62, incrementally since 5d6e7fd, separating the main merge and quest-link cleanup.
The prior hidden-job signing-identity finding is resolved in the inspected code. In rs/moq-net/src/model/origin.rs:3022–3031, reading an unnamed hidden job's instance binds it; lines 3075–3102 refuse announcement once that identity is spent instead of advertising the signer's bytes under another identity. C and moq-ffi no longer accidentally bind hidden jobs during handle creation. JS mirrors the read-or-announce binding and refusal behavior.
The follow-up requeue preserves the shared pending request rather than failing only a stale front: Request::drop hands it back through ServeState::requeue and RequestQueue::requeue. The Rust and JS regressions cover requesters arriving both before and after the claim moves, with a replacement job answering them. No new actionable findings in these substantive updates or their binding/documentation changes.
Direction: retain the observed-identity binding and shared-request retry. No additional changes requested from this pass.
Verification limits: GitHub-only static review of the incremental implementation, surrounding lifecycle/queue logic, changed tests, bindings and documentation. No commands, builds, tests, binding regeneration, cryptographic end-to-end test, browser lanes or interop run; author-reported validation was not independently reproduced. Inherited main changes were not re-reviewed wholesale. Rechecked open/non-draft state, head/base and existing reviews immediately before posting.
…eover # Conflicts: # quest/m2/sequence-reuse.md
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This pass reviewed the three origin/main merges since the prior reviewed head (c76e9872b). The PR's own patch is unchanged: git patch-id over git diff 08a1f8ac2 a8aeafded and git diff ded9eb52 cceb38e3b is identical (4976e711…), and each merge's diff against its main parent is exactly this PR's 102-file diff, so no conflict resolution altered or dropped anything from the branch's contribution.
- Merged
origin/mainat08a1f8ac2,118cbb17aandded9eb52— 497 main-owned files. None of the PR's core implementation is touched:model/origin.rs,route/node.rs,js/net/src/origin.ts,claim_continues.rsandorigin-request.test.tsare absent from the delta. - Main's edits in the 27 PR-touched files are peripheral — the
ietf/litepublisher and subscriber,moq-relay/src/cluster.rs, the binding smoke tests and the guides, carrying main's own renames (abort→reject,WindowPush→WindowEventPush,CACHE_EXPIRY→DEFAULT_EXPIRY,Moq.socket→Moq.Socket, the new frame producer/consumer types, andDatagramno longer re-exported frommodel/mod.rs). - The lending rule and the takeover removal still hold at HEAD — the claim-continuation text is intact in
doc/lib/js/net.md,doc/lib/rs/moq-net.md, the C and Dart guides anddoc/concept/identity.md, and noRouteEvent::Takeover,ANNOUNCE_TAKEOVERor three-argumentforward(reference remains underrs/.
Verification: bun test src/origin-request.test.ts src/integration.test.ts in js/net passed (162 tests, 0 failures) against the merged tree, and all 24 review threads are resolved. A local cargo check could not run because the sandbox's build-directory lock is held by a leftover process, so the Rust side rests on the byte-identical patch plus CI, whose Check run was in progress on cceb38e3b.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏
…eover # Conflicts: # rs/moq-net/src/model/origin.rs
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
This pass reviewed the single merge pushed since the prior review (59d06f0f3, merging origin/main at 979a79492); the commits it brings in from main are not this PR's own work.
- Stats-prefix exclusion absorbed — the branch takes main's
RouteEntry.excluded,OriginState::authorizes,Producer::exclude/publishable, the routed subscriber'sadmits(origin, …), and the relay'spublisher()exclusion of the stats subtree for non-peer grants. - Only the required field lands in the PR's route entry — the PR's own contribution changes by
excluded: self.excluded.clone()in theRouteEntrythatAnnouncing::announcebuilds, since that literal is the branch's rewrite of main's. - Lending now respects the claim's authorization — the merge also hand-added
.filter(|entry| entry.authorizes(path))tolender, so a job takes a claim's instance only when the claim's scope and exclusions admit the job's path. That is the same gate main had on thetakeover()path this PR deletes, so the rule survives its replacement rather than disappearing with it.
cargo check -p moq-net --all-features passes on the merged tree; the full unit suite exceeds the sandbox shell cap, so CI's Check is the authority.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Problem
A worker claims a prefix and answers each request by publishing that path's exact route. The job minted a fresh instance, so the first subscription through the claim was reset as soon as the exact route landed, on every wire. lite-07 papered over this with ANNOUNCE_TAKEOVER (moq-dev/moq#6994): a predecessor Announce ID that every origin, relay and binding had to track. That was a second continuity rule next to the instance, and the maintainer chose the instance (2026-10-31).
Approach
A claim lends its instance to its jobs.
Request::routewithout a named instance binds the claim's current instance and epoch when the route first announces or itsinstance()is first read, not when it is created. A hidden job that never read it joins whatever instance the claim holds once it is ready.announce()fails withUnroutableand its request goes back on the claim's queue, every requester still waiting, so the worker prepares the job anew (Request's drop andServeState::requeueinorigin.rs;requeueJobin JS). This holds in Rust, JS, C and moq-ffi alike; moq-c and moq-ffi read a route's instance only when asked or once it announces.A job's end moves the claim on.
OriginState::advanceinrs/moq-net/src/model/origin.rs).Unroutablewithout waking the worker.Node::homeandOutbox::pushinrs/moq-net/src/route/node.rs).Removed:
RouteEvent::Takeover;Claim::continues, theOwnClaimspredecessor);PendingFrontvia/first,Selected::continue_with,RouteEntry.takeover);fuzz::takeover_routeand the two takeover benches.origin/lendreplacesorigin/takeover.JS mirrors the Rust design in
js/net/src/origin.ts(a claim'sHandoff, bound at first announce, advanced before the retraction reroutes). A peer's request pinned to an instance and waiting on a job is now served by that job's route.BroadcastCacheandconnection/forward.tsneeded no change: the cache keys by instance.Docs and drafts:
doc/concept/identity.mdrewrites the lazy-job lifecycle.Tests:
origin.rs): a withdrawn job moves the claim; a hidden job takes the claim's instance when it announces; a job that read its instance is refused once the claim moves, and the worker gets the request again; a request pinned to an ended job is refused; a pinned request reaches its job after a sibling moves the claim; a claim and its job end independently; named instances are neither lent nor moved.rs/moq-net/tests/integration/claim_continues.rs, replacingannounce_takeover.rs):W -> R -> Echains on lite-06 and lite-07, with the claim beneath the path and at it; a restarted job; two workers; an unrequested publish; an older downstream; an older upstream hop; a diamond; late snapshots.rs/moq-cli/tests/integration/instances.rs):moq_mux::Sourcereads a catalog and its pinned media through a claim over a real lite-07 relay on its first request, and the pin is refused once the media job ends.restart.rs(claimed) anddrained_claim.rsnow run only for older wires and moq-transport.js/netand@moq/watch. The binding smoke tests (C, Go, Kotlin, Dart, Swift, Python) now expect a job to carry its claim's instance.Impact
origin::RouteEvent::Takeoveris gone; remove its match arms.Start,Update,RestartandEndremain. The JS takeover variant was internal and the API report is unchanged.Request::route/request.route()without an instance now continues a minting claim's instance instead of minting its own.Route::instancebinds it, and itsannounce()then fails withUnroutableonce the claim has moved past it. A job that never reads it binds at its first announce.instancechanges when a job holding it ends, which readers see as a restart of the claim.MoqRoute::instance_id()(and the Go, Python, Swift and Kotlininstanceaccessors over it) reads the route's current instance on each call instead of the one minted at creation; once cancelled it returns the last one read or announced under, or an empty string for a hidden route that did neither.moq_route_instancereads the route's current instance on each call, and the string it fills now borrows the route's storage only until the nextmoq_route_instanceon that route ormoq_route_free. Copy it out before calling again.moq-lite-07-wip) is unpublished. moq-transport and lite-06 and earlier are unchanged: they mint an instance per announcement, so their first subscription through a claim still resets. The exception is a claim at the answer's own path, where nothing new is announced.Stacking
a8aeafded7, its follow-up merges, and59d06f0f3bbring the branch up toorigin/mainat979a794923. Two conflicts:quest/m2/sequence-reuse.md: main finished and deleted that quest (moq-dev/moq#8202); this branch had only dropped a link from it, so the deletion stands.rs/moq-net/src/model/origin.rs: moq-dev/moq#8194 changedOriginState::takeover, which this PR replaces withlender, to checkRouteEntry::authorizes(scope minus the new excluded subtrees).lendernow applies the same check, so a prefix route lends its instance only for paths it may serve; main'stakeoveris dropped.e3a8c5743fmergesorigin/mainat76545a6b67after moq-dev/moq#8139 moved relay tests ontomemory::Net.goaway_cluster.rsconflicted only onnext_update, which main deleted, so main's version stands. TheTakeoverarm this PR removes is also gone from main's newmemory::recordhelper.10a9767a6fmergesorigin/mainatfe00d62cc6without conflicts.baecc27d4bisquest check --fix, which drops the newquest/m2/route-roles.md's## Requiredlink to this PR's finished quest.Built on moq-dev/moq#8023, which has merged. The PR merges
origin/mainat6f66d5593a. Every conflicting file was unchanged on main since #8023's head2a7e718e03, which this branch had already merged, so each one keeps this branch's version.quest check --fixdroppedquest/m2/sequence-reuse.md's link to the deleted quest. The diff now shows only this PR's change.Decisions
0b70a604ffcoalesces ROUTE_REQUEST per identity and hands writes over only after prior ones flush;2a7e718e03gives route outputs a turn beside a reply flood./quest-complete).announce()then refuses withUnroutableif the claim has moved on, and the worker prepares the job again. The signer stays valid, and only jobs that read their instance can be refused. Rejected: (b) document only, signing a hidden job afterannounce()or naming its instance, which leaves the trap in place; (c) bind every job at creation, which fails any job prepared while a sibling ends, even one that never signs. This settles the Codex review's medium finding atrs/moq-net/src/model/origin.rs:3048.moq_route_instancereturns a string borrowed until the nextmoq_route_instancecall on that route ormoq_route_free✅ (maintainer, 2026-11-07). Not chosen: a caller-provided buffer; keeping every string until free.Choices made without asking
Request's drop, under the queue lock that a removal would take, so no handler can pop the request while the refused job still holds it.quest rmdropped the links; the prose mentions now point here.Validation
On
59d06f0f3b, throughquest gate:just cipassed every module excepttest js, whose only failure is the known order-dependenta connection samples the transport only while a reservation reads the estimate(js/net/src/connection/bandwidth.test.ts, see Follow-ups). It fails identically (1 fail) in this worktree withjs/net/srcchecked out fromorigin/main, and passes when run alone.cargo test -p moq-net --all-features --libpassed (3098) after porting theauthorizescheck.On
9e40dbae51throughc76e9872bf, throughquest gate:just cipassed every module except three.check python(a docstring line too long) andcheck dart(stale bindings, since the UniFFI checksum covers doc comments) were fixed in the next two commits, and thedart, thenpythonandpymodules passed on rerun.test jsfailed one test outside this change,a connection samples the transport only while a reservation reads the estimate(js/net/src/connection/bandwidth.test.ts). It fails the same way in this worktree withjs/net/src/origin*.tsreverted tobaecc27d4b, passes in isolation, and the wholejs/netsuite passes in a fresh checkout oforigin/mainand ofbaecc27d4b, so it depends on the order bun reads test files in, not on this change. Onc76e9872bf,just cipassed every module except that sametest jsfailure.cargo test -p moq-net --all-featurespassed (3087 unit tests, includinga_job_that_read_its_instance_is_refused_once_the_claim_moves).Earlier:
On
baecc27d4b, throughquest gate:just cion10a9767a6fpassed every module exceptcheck quest.baecc27d4bfixed that, andCHECK_MODULES="quest markdown" just cithen passed.On
5d6e7fd9ac, throughquest gate:just cipassed, as it did on the mergee3a8c5743f.a_job_is_read_before_its_claims_restart(rs/moq-net/src/route/node.rs) failed before5d6e7fd9ac, withliveread beforelive/x.Earlier:
On
41ecad4b40(after merging #8023 at2a7e718e03andorigin/mainata9d7925123), throughquest gate:just check origin/main: every module passed (test rs: 11164 passed, 145 skipped). swift skips off macOS.quest check: pass.Earlier:
On
c7e9a828d5/c1d875e480(after mergingorigin/mainat9920a3f550), throughquest gate:just check origin/mainonc7e9a828d5: every module passed except two.test rsdid not compile: main's newlite07_servinghelper lackednode. Fixed inc1d875e480, thenCHECK_MODULES=rs just check origin/mainpassed (11158 passed, 145 skipped). This also clears CI'sCheckfailure,moq-relay config::tests::repository_configs_load, which main fixed in moq-dev/moq#8144.test js: 1 failure,lite-07 answers a bounded window that resolves empty and FINs in reply(js/net/src/lite/publisher.test.ts), a flake in main's code that moq-dev/moq#8150 quarantines. It passed 5 of 5 runs of the whole file in isolation.quest check: pass.Earlier:
On
915074a36d, throughquest gate:just check: every module passed excepttest rs, whose only failure ismoq-relay config::tests::repository_configs_load: it parses every tracked TOML undertest/as a relay config and rejectstest/audio-quality/clients/js/bunfig.toml, which main added in moq-dev/moq#8078 (unknown field test).cargo test -p moq-relay --lib repository_configs_loadfails the same way onorigin/mainatb5805859aa, so it is pre-existing; no open PR fixes it yet. swift skips off macOS.quest check: pass.On
335ebe3280, before the latest merges: targeted nextest (moq-net,moq-c,moq-ffi,--all-features, filtered to instance, route, job and claim tests) gave 422 passed, includinga_hidden_job_takes_the_claims_instance_when_it_announces,dynamic_broadcast_request(moq-ffi) anddynamic_serves_a_request_under_a_prefix(moq-c).Earlier, on
eea486e0e7, before the merges:just test interop --all --publishers rust,python,go,cpp --subscribers rust,python,go,cpp,js-native-node,js-native-bun,c,gstgave 159 passed, 0 failed, 22 skipped. The skips are JS lite-07 cells pendingquest/m2/cluster-routing/routes/js.md. The moq-cli real-player claim test passed 10 runs out of 10. Neither was rerun after the merges.Left to CI
Browser-driven lanes were not run on this host:
js(headless Chromium) publisher and subscriber cells ofjust test interop --all;--mediabrowser checks;just test harness,just test media,just test audio-quality,just test video,just test smokeandjust bench-browser.Swift tests run only on the macOS platform CI.
Follow-ups
bandwidth.test.tsdepends on test-file order (recommend planning it):a connection samples the transport only while a reservation reads the estimateswaps the globalWebTransportand the shared connection pool, and fails deterministically when bun readsjs/net/srcin some directory orders (see Validation). It passes onorigin/mainin a fresh checkout, so CI does not show it yet.A reordered moq-transport restart folds into the old instance. On a
solicit = falsesession, a publisher restart's new PUBLISH_NAMESPACE can arrive before the old one's PUBLISH_NAMESPACE_DONE: they travel on separate request streams. The subscriber deliberately folds a repeat advertisement into the instance it holds (attach's occupied branch inrs/moq-net/src/ietf/subscriber.rs;separate_adverts_refcount_the_route,pathless_adverts_never_replace_the_source). A probe confirmed that the reordered restart produces no route event at all, so per-announcement instances do not resolve it, contrary to what the deleted quest's plan assumed. Options:solicit = falsesession, the publisher opens the new PUBLISH_NAMESPACE only once the peer has closed the old advertisement's request stream. This costs one round trip per restart, keeps the refcount rule and changes no wire.CodeRabbit findings on feat(net)!: lite-07 routes through the route layer #8023's code, confirmed but outside this PR (the threads on this PR say where; none are in lines this PR changes):
OwnClaimshas no split horizon (rs/moq-net/src/lite/publisher.rs, therouted::Answerbuild);AnnounceServe::pollresets the stream on a protocol violation instead of lettingControlclose the session;subscriber/routed.rsscans every route perRouteStartand every claim perActive(index by node ID);route/node.rshomere-collects departed identities on every flush, throughrouter.collect;publisher/routed.rsAnswer::pollloop has no cooperative budget;Rename the epoch draft's EPOCH to INSTANCE (
moq-instance). Its parameter names an instance but is called EPOCH, and the draft still orders different instances by UUIDv7 time. It is unpublished and unimplemented. The rename also touchesdoc/concept/standard.md, the cluster draft andquest/m5/ietf-cluster-peers.md.Models
Size: XL
Implementer: Claude Code, claude-opus-5-5, unknown effort
Implementer: Claude Code, claude-opus-5-5, medium effort
Implementer: Claude Code, claude-opus-5-5, high effort (iterate, S)
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, S)
Review
Rewritten by
quest reviewafter each round.01a1b9d9-683c-7681-bb23-c13ef3ad3761, round 2No actionable regressions were found in the merge’s interaction with the branch’s continuity logic, but builds and tests were not run, as instructed.