Repository navigation
test(net): the route model checks an announce budget and convergence on meshes with clients - #8479
Conversation
Quest-Claim: bb4dd9407519d139
…nes, rings, and clients Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on lines, rings, and a chorded ring with clients Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve RouteHandoff against main's hops: paths grow only through forwarding nodes and links, every mesh checks SimplePaths and NoCountToInfinity, and Budget states the mesh sweep's budget. Drop the TLC recipe change, which #8499 owns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sh/tla.sh passes a config's `Simulate:` line to TLC, so Chord.cfg runs as random simulation in Nightly. The route model's three largest configs take about an hour each on 4 workers with the budget counters, so each gets its own leg, and the TLA+ job's cap rises to 180 minutes. The sync commit moves to caa3dd8, which changed only node.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pre-merge checks:
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the full diff at 3f3a771 and checked the load-bearing claims against the code and tooling rather than the description alone.
Budgetaccounting —burst/churn,Sent,External/Internal, andPerChange. Every send site is counted exactly once (ChangeClaim,Connect,Disconnect,CollectviaExternal(Sent(rs));Deliverboth branches plus the ROUTE_END echo;DeliverAckboth branches), andExternalresets both counters only when the pre-state isQuiet, which is correct becauseburstthen counts a finished burst.- Budget matches the sweep —
4 * Cardinality(Dirs) * (1 + Cardinality(Claims))expands to8 * sessions * (1 + claims), the same formula asmesh_sweep.rs'sbudget()with one identity. Convergesonly inLine.cfg— the argument thatBudgetimplies convergence holds: after the last changechurnis fixed, so sends are bounded, deliveries consume queued messages and acks, and a run that never quiets would exceedPerChange * churn.Fairnessis not vacuous (a behavior that starves delivery satisfies neither disjunct).Extend/Paths— restricting growth toLinksand toServes(Last(p))is correct, not a weakening: a route's hops only ever extend from a serving node, andPathsalways contains<<Origin>>, soSimplePathsandLongeststay non-vacuous while client-terminated paths are excluded.- New configs —
Line,Ring, andChordnodes, links,Leaves, and bounds were checked node by node against their comments, and their bounds match the SKILL timing table. sh/tla.sh—read -ra simulateclears the array on an empty read (soChord.cfg's flags cannot leak into later configs), thesedpattern matches only a real\* Simulate:line, onlyChord.cfgcarries one, and the empty-array expansion is fine on the bash 4+ the script already requires (mapfile).- Nightly matrix — ran the route leg's real
git ls-files --cached --others --exclude-standard -- rs/moq-net/verify/route ':!…RouteHandoff.cfg' ':!…BehindDear.cfg' ':!…Ring.cfg': it yields exactlyBehind.cfg,Chord.cfg,Line.cfg. The per-config legs resolve to their one spec, the file parses as YAML, and${{ matrix.paths }}splits into the four pathspecs as intended. - Docs and quests —
python3 sh/coverage-tables.pypasses with the newSim::leaf/Statsrow, and a repo-wide grep finds no danglingroute-mesh-modellink after the quest deletion.
Two notes that need no action: the route leg still bundles Behind.cfg (25 min) with Line (13) and Chord (9), and the runtimes were measured on TLC 1.8.0/JDK 21 while Nightly is on 1.7.4/Java 8 — the 180-minute cap is the intended margin for that. Separately, git log caa3dd8926..HEAD -- rs/moq-net/src/route/ currently returns #8514, which only added Sim::shows assertions in route/tests/routing.rs, so no covered symbol changed and the sync commit still describes the model's match.
deepseek-v4.1-flash-1017 (free via Pullfrog for OSS) | 𝕏

Finishes
quest/m1/route-mesh-model.md: the route model (rs/moq-net/verify/route/RouteHandoff.tla) now checks an announce budget and convergence on meshes bigger than the triangle, including clients.Changes
burst), and the external changes made in that span (churn): claim changes, session opens and closes, collections.Budget == burst <= 4 * |Dirs| * (1 + |Claims|) * churn: 4 messages each way over each session per route identity and per claim, the same budgetmesh_sweep.rs(from moq-dev/moq#8477) holds the implementation to, with the model's one identity.Converges == <>[](Quiet /\ Settled)underLiveSpec(fairness: something in flight is eventually delivered). Sinceburstis part of the state,Budgetalready rules out a delivery cycle that never goes quiet, soLine.cfgis the only config that runs the liveness checker.Line.cfg(origin, 3 relays, a watching client),Ring.cfg(origin and 3 relays), andChord.cfg(a publishing client on a ring of 5 relays with a chord, plus 2 watching clients, 2 claims; random simulation only). Every config, including main'sBehind.cfgandBehindDear.cfg, checksSimplePaths,NoCountToInfinity, andBudget.Pathsnow grows only along links and through forwarding nodes, so a client ends a path and the 8-node chord stays cheap to enumerate.RouteHandoff.view.tslabels any mesh.sh/tla.shpasses a config's\* Simulate:line to TLC, soChord.cfgruns as random simulation. The budget counters make the triangle,BehindDear.cfg, andRing.cfgtake 40 to 60 minutes each on 4 workers (about 46,000 CPU-seconds for the route configs together), which one 4-core leg cannot fit in 120 minutes. Each of those three now gets its own leg, arouteleg takes the rest of the directory, and the TLA+ job's cap rises to 180 minutes, since Nightly still runs TLC 1.7.4 on Java 8 until moq-dev/moq#8499 lands.verify-routedocuments the new rules, configs, and runtimes, and moves the sync commit tocaa3dd8926(the last change underroute/, which touched onlynode.rs, whose seqno bump on a closed link the model leaves out). The quest file is deleted and its links removed.route-inductiveno longer requires it.Checks
TLC 1.8.0 on Java 21,
-workers 4,nice -n 19, 4 GB heap (the setup moq-dev/moq#8499 moves the recipes to), on a 24-core host loaded by other sessions, at the merged head.RouteHandoff.cfg(triangle)Behind.cfgBehindDear.cfgLine.cfg(+Converges)Ring.cfgChord.cfg-simulate num=1000 -depth 600just check: pass.quest check: pass.sh/tla.shon a small copy ofChord.cfg(simulated) and onjs/publish/verify/Publish.cfg(exhaustive): pass. The spec and configs are unchanged by the last merge of main.Decisions
Maintainer, via the /quest-iterate brief, 2026-11-11:
✅ Accept the two-identity reduction (rejected: a second origin identity in the spec, run in simulation).
✅ Drop the follow-up to measure TLC worker scaling on JDK 25; moq-dev/moq#8499 owns TLC speed. This PR's
just rs tla/just js tlarecipe change (JDK 25, 8 workers) is dropped in favour of chore: TLC runs on Java 21 at low priority, and plan two faster-checking spikes #8499 (rejected: keeping a second recipe change here).Budget: ✅ the model uses
mesh_sweep.rs's message budget, so both state one budget (rejected: restoring the model's tighter budget and tightening the sweep to match). Decided 2026-11-11 by the maintainer at the merge pick, which also accepted the nightly split into four route jobs at a 180-minute cap.Choices made without asking
Routerkeys all state by identity and updates an identity from its own state only. So each identity's messages, read on their own, form a run of the one-identity model, and a shared FIFO only removes orderings. Instead, the configs place the origin at the end of a line, inside a ring, behind a triangle, and on a client off a ring.mesh_sweep.rsstate one budget or change both. The model's own earlier budget (4 per direction per change, with no per-claim term) was tighter: the triangle's worst change used 20 of 24. The shared one is looser by the claim term, a factor of 2 to 3 here; a count to infinity still grows past any constant.SYMMETRY. Selection breaks ties by neighbour key, so no two relays are interchangeable. Only watching clients on the same relay are, and one client per relay covers them.Chord.cfg, whose flaps can cut the ring into a line).[]<>Quiet \/ []<><<Delivery>>_vars. WithWF_vars(...), TLC's ENABLED check never finished. This assumption is weaker than per-queue fairness, so it checks at least as much.burstandchurngrow every config's state space (the triangle from 3.5M to 24M distinct), since the counters are state. That is the cost ofBudgetalso implying convergence.Public API: none. Wire: none.
Models
Size: L
Implementer: Claude Code, claude-opus-5-5, unknown effort
Implementer: Claude Code, claude-opus-5-5, medium effort (iterate, M)
Review
Rewritten by
quest reviewafter each round.01a1cd2c-b03a-7c40-9b3f-b6f0321ad3f3, round 3The incremental diff restores the coverage mapping and introduces no actionable defects on source review; builds and tests were not run, as instructed..claude/skills/verify-route/SKILL.md:74:Restore the route coverage table removed during the merge;The head restores all eight coverage rows with repository-relative paths, retains the Sim::leaf/Stats row, and removes the duplicate sync paragraph.(Written by Claude Opus 5.5 with medium)