Skip to content

fix(net): widening an interest ends no live claim - #8540

Merged
kixelated merged 2 commits into
mainfrom
quest/m2/cluster-routing/routes/widen-false-end
Nov 12, 2026
Merged

kixelated merged 2 commits into
mainfrom
quest/m2/cluster-routing/routes/widen-false-end

Conversation

@kixelated

@kixelated kixelated commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m2/cluster-routing/routes/widen-false-end.md.

Problem

A client that widens its interest can see END for a claim its publisher still has. sweep_scoped_handoff hits it in 25 of 20,000 scenarios (first at 750).

Cause: show reports a vanished claim as moved (offline), not ended, only when a claim above it is shown with the same item. A claim presented at a narrow interest stands for whichever claim covers it. If that claim changed attributes while the client could not hear it (here the origin re-announced live/ while the client was cut off), the widened stream's first batch removes the stale live/b/z and shows live/ with the new item in the same recompute. The items differ, so the move reads as a withdrawal. Scopes, scenario order and session-close order are incidental: three nodes and no grant reproduce it.

Approach

  • Shown records presented: the claim sits at the root of a region its merged view was presented at, so it stands for whichever claim covers that region. A presented claim that vanishes counts as moved when anything above it is still shown, whatever its item. Any other claim keeps the item-equality rule, so a real withdrawal under a live claim still ends.
  • widening_past_a_changed_claim_moves_it_up is the case shrunk from scenario 750's 13 steps to 7 on three nodes. a_claim_withdrawn_under_a_live_one_ends pins the other side, which no test covered (dropping the item check entirely passed all 124 route tests).
  • MOQ_ROUTE_SCENARIOS sets how many scenarios sweep_scoped_handoff and sweep_handoff run (default: the gate's 150). Each sweep draws from one seeded stream, so a larger count replays the gate's scenarios and continues past them. handoff-revival now names it.
  • A sweep nextest profile (just rs test -P sweep ...) bounds a test at an hour instead of the default 60s, which kills a 20,000-scenario sweep at ~79s.
  • The cause is in interest handling that survives the route per scope (presentation and show), not in the Babel machinery it deletes, so this is a fix, not a residual_ pin.

Sweeps with the fix (MOQ_ROUTE_SCENARIOS=N just rs test -P sweep -p moq-net -E 'test(=route::tests::handoff::<sweep>)'):

  • sweep_scoped_handoff: 20,000 scenarios clean (79s). Before: 25 false ENDs.
  • sweep_handoff: first 16,316 clean (127s), which ends just before the known revival at 16,316 that handoff-revival owns.

Impact

  • Public API: none (the router is crate-private). Wire: none. js/net has no counterpart of this reader logic to change.
  • Behaviour: a presented claim that was itself withdrawn while the interest widened, with a claim still above it, reports Offline instead of End. The batch cannot tell the two apart, and Offline is the safe side: a clean end means only a deliberate publisher finish.

Alternatives

  • Drop the item check (any shown ancestor means moved): turns every withdrawal under a live claim into Offline. Rejected by the new test.
  • Compare a claim identity (production Claim::instance) instead of the whole item: the router is generic over the item and has no identity notion, and same-origin claims can share attributes.
  • Wait for the interest set: an edit in place still widens what is presented, so the same move happens; the test now guards it.

Choices made without asking

  • Replaced the quest's 13-step scoped case with the 7-step unscoped one, since it isolates the cause; the sweep still replays scenario 750 under MOQ_ROUTE_SCENARIOS.
  • The knob is an environment variable, matching MOQ_RESUME_CASES in model/resume_model.rs.
  • Added the sweep nextest profile rather than raising the default timeout, which .config/nextest.toml asks to keep tight.

Caused by

  • moq-dev/moq#8293 - decided a move by item equality, which misses a claim that changed while the client could not hear it

Follow-ups

None.

Models

Size: M
Implementer: Claude Code, claude-opus-5-5, medium effort

Review

Rewritten by quest review after each round.

  • Reviewer: Codex, gpt-6.1-sol, medium effort
  • Session: 01a1ceab-e24a-7f20-93b4-b5bc77baf175, round 1
  • Head: 62e812c
  • Base: origin/main at 13e3ad8
  • Score: 9/10, The targeted fix preserves ordinary withdrawal handling and adds focused regression coverage, though builds, tests, and model checks were not run as instructed.
  • Findings: none

(Written by Claude Opus 5.5 with medium)

🤖 Generated with Claude Code

kixelated and others added 2 commits November 11, 2026 23:14
…d, even if it changed

show() treated a vanished claim as moved only when the claim above it had
the same item, so a claim that changed while the client could not hear it
read as withdrawn once the interest widened, and the reader saw END.
A shown claim now records whether it sits at a presentation root; such a
claim moves up whenever anything above it is still shown.

MOQ_ROUTE_SCENARIOS sets how many scenarios the handoff sweeps run, and a
`sweep` nextest profile lets a long run finish.

Co-Authored-By: Claude <noreply@anthropic.com>
@kixelated kixelated changed the title Widening an interest ends no live claim fix(net): widening an interest ends no live claim Nov 12, 2026
@kixelated
kixelated marked this pull request as ready for review November 12, 2026 07:33
@coderabbitai

coderabbitai Bot commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cdf0a0b4-4519-422d-a292-d76a315e40a3
Commits

Reviewing files that changed from the base of the PR and between d3b6c25 and 62e812c.

Files selected for processing: 7

  • .config/nextest.toml
  • quest/m2/cluster-routing/routes/handoff-revival.md
  • quest/m2/cluster-routing/routes/interest-set.md
  • quest/m2/cluster-routing/routes/widen-false-end.md
  • rs/moq-net/src/route/mod.rs
  • rs/moq-net/src/route/tests/handoff.rs
  • rs/moq-net/src/route/tests/mod.rs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the full PR at 62e812c (both commits, 7 files) and re-ran the routing suite locally.

  • presented on Shown — records whether the row sits at the root of a floor region of the merged view, so a row that may stand for whichever claim covers that region can be told apart from a claim at its own path. Recording it on the stored entry is what makes the classification possible after the region itself is gone.
  • Relaxed move rule — moved now accepts was.presented || now.item == was.item, so a presented row that vanished while any strict ancestor is still shown counts as moved up instead of ending. Any other row keeps item equality, so a real withdrawal under a live claim still ends.
  • Regression tests — widening_past_a_changed_claim_moves_it_up shrinks the scoped sweep failure to seven steps on three nodes, and a_claim_withdrawn_under_a_live_one_ends pins the item-equality side that no test covered before.
  • Scenario knob and sweep profile — MOQ_ROUTE_SCENARIOS lets both handoff sweeps run past the gate's 150 from the same seeded stream, and [profile.sweep] gives that opt-in run a bounded hour instead of the tight default timeout.
  • Quest completion — widen-false-end.md is deleted with both inbound links, leaving no stale references (the slug grep is clean).

The trade-off in Impact is sound: a presented row can be withdrawn itself while an ancestor still announces its path, so the batch cannot prove a publisher finish, and Offline is the conservative side given that a clean end means only a deliberate finish. I traced the staleness question too: every floors-key change, view swap, prune, retire, or ctx change marks the id's claims all-dirty, and the #[cfg(test)] incremental-view assert compares the full recompute including presented, so the stored flag cannot drift.

Verification: cargo test -p moq-net --lib route::tests → 89 passed, 2 ignored (8.1s); MOQ_ROUTE_SCENARIOS=1000 on sweep_scoped_handoff → clean in 9.1s, which covers scenario 750, the reported first failure (the 20,000-scenario run was started here but the sandbox cut the background process off). Both new tests fail under mutation: removing was.presented || makes the widening test report a false end, and replacing the predicate with a bare is_some() makes the withdrawal test report Offline instead of End. rustfmt --edition 2024 --check is clean on the changed Rust files.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

@kixelated
kixelated added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit 8c2302b Nov 12, 2026
12 checks passed
@kixelated
kixelated deleted the quest/m2/cluster-routing/routes/widen-false-end branch November 12, 2026 10:03
kixelated added a commit that referenced this pull request Nov 13, 2026
…00 nightly

Scenario 16316 of sweep_handoff failed its false-END assertion, not the
revival one, at 688312b. #8540 fixed it: undoing that fix's one line
brings the failure back. The shrunk case is now a plain test, and nightly
runs the handoff sweeps over 20,000 scenarios.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant