Skip to content

quest: retain timer-only doubt over a fresh publisher's exemption - #8735

Merged
kixelated merged 9 commits into
mainfrom
quest/m2/cluster-routes/fresh-publisher
Nov 15, 2026
Merged

kixelated merged 9 commits into
mainfrom
quest/m2/cluster-routes/fresh-publisher

Conversation

@kixelated

@kixelated kixelated commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Finishes quest/m2/cluster-routes/fresh-publisher.md: the maintainer retained timer-only doubt, so this PR deletes the quest and keeps its measurements in the epic's Plan item 27.

Problem

Under the path rule (moq-dev/moq#8622), a publisher that reconnects waits out the one-second doubt its own closed session cast: 1,050 ms in a 24-relay clique and 1,875 ms in a ring, against 50 ms and 325 ms for the distance rule. The quest asked whether a narrow exemption is safe, and whether it actually shortens those waits: a relay moves at once onto the publisher's own route, offered on an authenticated session the publisher opened after the doubt began.

Approach

  • Measured, then declined. Earlier heads of this PR (last at a8625e4) modeled the exemption in RouteHandoff.tla (Exempt, Fresh.cfg beside its timer-only control Reconnect.cfg) and ran it in the router as the test-only comparison rule Rule::FRESH (Doubt::fresh, since). The maintainer retained timer-only doubt, then picked trimming that code: moq-dev/moq#8764 slated RouteHandoff.tla for deletion, so nobody would rerun it, and git keeps it. The router, model, verify-route skill, and benchmarks now match main.
  • Quest: deleted. Epic Plan item 27 records the decline with the results table below.
  • Incidental fix: moq-srt's a_publisher_marker_keeps_an_srt_receiver_playing drives real SRT sockets but never declared kio::clock::host_time(). Under load, its jitter wait ran out on the host clock and KIO_DENY_HOST_TIMERS failed the test (seen in just check; it passes alone).

Results

The exemption is safe in every bound checked, but it frees only the publisher's own relay. Every other relay still doubts the publisher and refuses the route forwarded to it.

24-relay reconnect cases (benchmark_route_rules; right is until every relay routes to the publisher; dark counts relay-rounds with no route while a path exists):

Mesh, change Rule Right Dark Route msgs Claims Offline
clique, rejoin path 1,050 ms 984 1,105 96 0
clique, rejoin path+fresh 1,025 ms 921 1,105 96 0
ring, rejoin path 1,875 ms 1,512 118 96 0
ring, rejoin path+fresh 1,800 ms 1,380 120 88 0
sparse (16 meshes), rejoin path 1,372 ms 1,194 164 100 4
sparse (16 meshes), rejoin path+fresh 1,285 ms 1,056 187 91 4
clique / ring, move both 1,100 / 1,850 ms 1,032 / 1,488 1,612 / 117 2,124 / 92 92 / 0
  • On a rejoin, the home relay recovers within one hop of delay instead of after the timer. Every other relay still waits for its own timer, so the mesh recovers only 25 to 87 ms sooner.
  • On a move, the exemption never fires: the new relay opens its session before the DEAD reaches it, so the session predates the doubt.
  • In the ring, DEADs echoing back round it push each relay's doubt as late as 1,575 ms, far past when the first DEAD would have let it lift.

Churn (benchmark_route_churn, 400 cases, the publisher's link flapping most):

Rule Loops Stale Dark Route msgs Claims Offline Unavailable
path 0 3,312 162,654 8,785 5,698 3,336 3,110
path+fresh 0 3,384 116,404 13,646 10,524 6,000 3,324

The exemption cuts dark observations by 28%, but costs 55% more route messages, 85% more claims sent, and 80% more claims seen going offline. Timer-only doubt damps a flapping publisher; the exemption shows each flap.

Decisions

  • Decided 2026-11-14 (maintainer, /quest-complete): ✅ retain timer-only doubt (rejected: adopt the exemption).
  • Decided 2026-11-15 (maintainer, /quest-complete): ✅ trim, then merge (rejected: merge as is; delete the PR). Keep the results table in the epic, the quest deletion, and the moq-srt fix; drop Rule::FRESH/Doubt::fresh, since, Exempt, Fresh.cfg, and the benchmark rows.

The rest were taken by the agent under /quest-afk, for the maintainer's review.

Impact

Alternatives

  • Exempt the publisher's own route on any session ("own"): it would catch the move case, but it still frees only the relay the publisher is attached to.
  • Exempt any route on a fresh session ("session"): rejected by the quest. A new transport session does not show the origin is alive.
  • Exempt a forwarded route through a newer session than the dead hop's: this would reach the remote relays, but it is unmodeled. The flapping publisher's origin rule (feat(route): a dual-homed publisher's other links survive doubt about it #8734) covers the same ground.

Follow-ups

None.

Validation

  • This head: just check passes (321 Rust tests in the changed crates, every module), quest check passes (512 documents), and trial merges with lite-07 sessions run the path router #8731's and feat(route): a dual-homed publisher's other links survive doubt about it #8734's heads show only the conflicts listed under Impact.
  • The measurements, at a8625e4 where the code lived:
    • TLC (the pinned tla2tools.jar and Community Modules, through sh/tlc.py). Triangle, exhaustive, every invariant including NoStaleReturn: "none" 1,490,147 distinct states, "fresh" 1,822,789, "own" 1,750,688, "session" 2,909,134, no error. Kite (a third relay behind relays 1 and 2): all four variants, the control included, break NoStaleReturn through two closes and no reconnect, Twice.cfg's accepted case; without that invariant, "fresh" showed no violation in 12.9M distinct states, every ordering to depth 14, before a 45-minute cap.
    • Router with Rule::FRESH: all 42 both! fixtures, sweep_handoff, sweep_scoped_handoff, sweep_requests_reach_a_quiet_origin, and the residual fixtures passed at MOQ_ROUTE_SCENARIOS=2000; benchmark_route_rules and benchmark_route_churn produced the tables above. just check and just ci passed: 13,134 tests.

Caused by

  • moq-dev/moq#8081 - added the host-timer guard and marked the sibling real-socket SRT test, but not a_publisher_marker_keeps_an_srt_receiver_playing

Models

Size: L
Implementer: Claude Code, claude-opus-5-5, unknown effort
Implementer: Claude Code, claude-opus-5-5, low effort (iterate, XS)
Implementer: Claude Code, claude-opus-5-5, unknown effort (iterate, S)

Review

Rewritten by quest review after each round.

  • Head: 5722a22

  • Base: origin/main at 98ed0f8

  • Risk: medium, the base has no RISK.md to name what is always high or customer-facing, so every change is medium

  • Classified: medium by default

  • Reviewer: Codex, gpt-6.1-sol, xhigh effort

  • Session: 01a1de2f-1db8-7581-9b35-113e9beca0bf, round 1

  • Score: 9/10, No actionable defects remain; the trimmed diff preserves results and appropriately enables host time for the real-socket test, though builds and tests were not run.

  • Rounds: 4; findings 2 (low 2): fixed 1, refuted 1, open 0

  • Findings: none

  • The last round's findings, at a8625e4:

    • Refuted: low, quest/m2/cluster-routes/flapping-publisher.md:41: Update the remaining reference to the deleted quest; The author's refutation is supported by 154bb658c:quest/m2/cluster-routes/flapping-publisher.md:12–27, where the rewrite removes the obsolete quest reference.

🤖 Generated with Claude Code

(Written by Claude Opus 5.5)

kixelated and others added 3 commits November 14, 2026 03:14
The test drives real SRT sockets, so its jitter engine waits in real time.
Under load that wait ran out, and KIO_DENY_HOST_TIMERS failed the test, as
its sibling already declares with `kio::clock::host_time()`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The route model gains `Exempt`: "fresh" lets a relay move at once onto a
doubted publisher's own route, offered on a session the publisher opened
after the doubt began, and "own" and "session" each drop one condition.
`Fresh.cfg` checks it beside its timer-only control `Reconnect.cfg`. The
router runs it as the comparison rule `Rule::FRESH` in test builds only,
and the route benchmarks compare it.

It is safe in every bound checked, but frees only the publisher's own
relay: the 24-relay reconnect cases end 25 to 75 ms sooner, a publisher
that moves gains nothing, and churn sends 55% more route messages. The
quest now waits on the maintainer's call, recommending timer-only doubt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated kixelated changed the title Model and measure a fresh publisher's doubt exemption test(moq-net): model and measure a fresh publisher's doubt exemption Nov 14, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kixelated
kixelated marked this pull request as ready for review November 14, 2026 14:22

@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 critical issues. One small guard for the model is suggested inline.

Reviewed changes

This run reviewed the full PR: a test-only doubt exemption for a reconnecting publisher in path::Router, the matching Exempt comparison in RouteHandoff.tla, and the documents that record the measurements.

  • Router exemption (Rule::FRESH): Router::barred skips doubt for a path that names only the peer, when the peer's session number is above since, the opened count saved when the doubt began. since is set only when a node goes from not doubted to doubted, so later DEADs do not reset it, and it is removed when timeout fires the doubt. Both former doubts_path callers, select and hopes, now go through barred.
  • Shipped behavior unchanged: outside #[cfg(test)], the only change moves the deadline update into Router::suspect, which fail and the GOAWAY path now share. This is a pure refactor.
  • Model: Exempt adds Own, Exempted, and Barred, plus a per-node fresh set. Connect sets it through Opened, Closed clears it, and Lift clears it under "fresh", which matches the router's since lifecycle. Every existing config pins Exempt = "none". Reconnect.cfg is the control for Fresh.cfg.
  • Benchmarks: COMPARED adds Rule::FRESH. RULES, which the merge-gate tests use, is unchanged, and sh/gh/nightly.sh does not depend on rule names.
  • Docs and quest: the verify-route skill's sync commit moves to 12537d1c2 (#8622's squash; no file in route/path.rs or route/reader.rs changed after it). The skill gains a coverage row for barred, fresh, and suspect (sh/coverage-tables.py passes). The quest is rewritten as the maintainer's adopt-or-retain call.
  • moq-srt test fix: a_publisher_marker_keeps_an_srt_receiver_playing now declares kio::clock::host_time() for its real SRT sockets, as rs/AGENTS.md requires.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

@coderabbitai

coderabbitai Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

No actionable comments were generated in the recent review.

Walkthrough

The routing changes add a test-only exemption for a doubted neighbour’s own route when it is offered on a session opened after doubt began. The formal model defines fresh, own, and session exemption rules. Model configurations and router benchmarks cover the fresh rule. Documentation reports model-checking and router-test results and records a pending adoption decision. The SRT discontinuity test switches to the host clock before using real sockets.

Merge Risk: Minimal · up to edade

This change adds a test-only comparison rule, a model variant and documentation, and fixes a clock declaration in one SRT test. Shipped routing behavior and the public API are unchanged, so production risk is minimal.

Pre-merge checks: Passed 4

Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title names the main change: the PR retains timer-only doubt over the fresh-publisher exemption and deletes the quest. The raw summary shows the code changes (Rule::FRESH, Exempt model, Fresh.cfg)…
Description check Passed The description is related to the changeset. It explains the quest deletion, the retained timer-only doubt decision, the measured results table, the moq-srt host-clock fix, and the trimmed router, mod…
Full details: Title check

Explanation

The title names the main change: the PR retains timer-only doubt over the fresh-publisher exemption and deletes the quest. The raw summary shows the code changes (Rule::FRESH, Exempt model, Fresh.cfg) are mostly reverted or trimmed in the description, while the summaries describe the code as still present. The title still matches the stated decision, which is the PR's main purpose.

Full details: Description check

Explanation

The description is related to the changeset. It explains the quest deletion, the retained timer-only doubt decision, the measured results table, the moq-srt host-clock fix, and the trimmed router, model, and benchmark code. It is detailed and on-topic.

Finishing Touches

Simplify code

  • Commit to this branch
  • Create a new PR
  • 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

Recent review info

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: 482c5177-86f8-405c-9eca-c92f5b6c0161
Commits

Reviewing files that changed from the base of the PR and between 12537d1 and edadef3.

Files selected for processing: 24

  • .claude/skills/verify-route/SKILL.md
  • quest/m2/cluster-routes/README.md
  • quest/m2/cluster-routes/fresh-publisher.md
  • rs/moq-net/src/route/path.rs
  • rs/moq-net/src/route/tests/compare.rs
  • rs/moq-net/src/route/tests/mod.rs
  • rs/moq-net/verify/route/Blind.cfg
  • rs/moq-net/verify/route/Dead.cfg
  • rs/moq-net/verify/route/Drain.cfg
  • rs/moq-net/verify/route/DrainRing.cfg
  • rs/moq-net/verify/route/Early.cfg
  • rs/moq-net/verify/route/Fresh.cfg
  • rs/moq-net/verify/route/HoldCycle.cfg
  • rs/moq-net/verify/route/Lag.cfg
  • rs/moq-net/verify/route/LagWide.cfg
  • rs/moq-net/verify/route/Loop.cfg
  • rs/moq-net/verify/route/Overlap.cfg
  • rs/moq-net/verify/route/Prefer.cfg
  • rs/moq-net/verify/route/Reconnect.cfg
  • rs/moq-net/verify/route/Ring.cfg
  • rs/moq-net/verify/route/RouteHandoff.cfg
  • rs/moq-net/verify/route/RouteHandoff.tla
  • rs/moq-net/verify/route/Twice.cfg
  • rs/moq-srt/src/server.rs

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 0 remain after this review.

@kixelated

Copy link
Copy Markdown
Collaborator Author

Heads-up from #8734, which works on flapping-publisher: it deletes quest/m2/cluster-routes/fresh-publisher.md, as that quest said to do to whichever PR landed first. Whichever of the two merges second will need to settle that file.

Your measurement shaped #8734's rule. I measured the broad origin exemption on the same churn script: compared with timer-only doubt, it sent 160% more route messages, sent 189% more claims, and took 155% more claims offline. #8734 therefore keeps the timer for a link that returns during the doubt. It spares only a dual-homed publisher's other links and a publisher that moves.

On your one-home script, that rule matches timer-only doubt within 3% (9,049 vs 8,785 route messages, 3,452 vs 3,336 claims offline). With two homes, it has 14% less time unrouted, for 22% more route messages and 3% more claims offline. The decision and both tables are in #8734's body.

(Written by Claude Opus 5.5)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pullfrog[bot]
pullfrog Bot previously approved these changes Nov 14, 2026

@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

This run reviewed the one commit added since the prior Pullfrog review.

  • Rejected unknown Exempt values: RouteHandoff.tla gains ASSUME Exempt \in {"none", "fresh", "own", "session"} after the CONSTANTS block, so a misspelled value fails at TLC startup instead of silently acting as "none". This addresses the earlier inline suggestion.

Pullfrog  | View workflow run | Using claude-sonnet-5-5 | 𝕏

The maintainer declined the exemption on 2026-11-14. Rule::FRESH and the
model's Exempt stay as comparisons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pullfrog[bot]
pullfrog Bot previously approved these changes Nov 14, 2026

@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 — the delta since 3c47bcf is docs and quest bookkeeping only.

  • Recorded the decision — the verify-route skill and epic README now say the maintainer declined the fresh-publisher exemption on 2026-11-14, linking #8735.
  • Deleted the quest — fresh-publisher.md is removed, matching the retained timer-only doubt. The only remaining mention is plain code formatting in flapping-publisher.md, which #8734 rewrites.

Pullfrog  | View workflow run | Using claude-sonnet-5-5 | 𝕏

kixelated and others added 3 commits November 14, 2026 23:44
The maintainer retained timer-only doubt, so the comparison rule goes:
Rule::FRESH, Doubt::fresh and its `since` bookkeeping, the model's Exempt
variant with Fresh.cfg and its control Reconnect.cfg, and the benchmark
rows. The router, model, skill, and benchmarks take main's versions; the
results stay in epic Plan item 27.

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>
@kixelated kixelated changed the title test(moq-net): model and measure a fresh publisher's doubt exemption quest: retain timer-only doubt over a fresh publisher's exemption Nov 15, 2026
@kixelated
kixelated enabled auto-merge November 15, 2026 07:53

@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 — the delta since a8625e4 trims the PR to its final shape.

  • Dropped the declined exemption's code — Doubt::fresh, since, barred, Rule::FRESH, the Exempt model constant, Fresh.cfg, Reconnect.cfg, and the verify-route skill edits are gone, so route/path.rs and RouteHandoff.tla match main. No dangling references remain.
  • Recorded the results in item 27 — quest/m2/cluster-routes/README.md now holds the declined-decision paragraph and the measurements table beside the existing measurements.
  • Kept the moq-srt fix — the test declares kio::clock::host_time() for its real sockets.

Pullfrog  | View workflow run | Using claude-sonnet-5-5 | 𝕏

@kixelated
kixelated added this pull request to the merge queue Nov 15, 2026
Merged via the queue into main with commit 466f2f6 Nov 15, 2026
9 checks passed
@kixelated
kixelated deleted the quest/m2/cluster-routes/fresh-publisher branch November 15, 2026 08:26
kixelated added a commit that referenced this pull request Nov 15, 2026
- quest/m2/cluster-routes: main declined the fresh publisher's exemption
  (#8735) and deleted its quest, so its link to the switch goes with it;
  item 28 keeps main's text with the switch linked to its PR.
- `waits_out_the_doubt` writes its groups as #8638's start-addressed API
  does (`append_next`, `at_start`, `close`), as the other route-change
  tests now do.

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