fix(net): record the placeholder-rewrite decline like every other - #3091
Merged
Conversation
Rebasing onto a dev that carries #3065 merged cleanly and still lost an invariant. That PR made every path out of `start_announce` that declines an announce record it first, so a declined announce keeps its path and a later `ANNOUNCE_END` retires nothing rather than a stranger's route. This branch adds a decline path #3065 never saw, where rewriting a placeholder hop would name this session twice, and git took it verbatim: five returns, four records, no conflict and no failing test. Record it too, and pin the rule with a test that walks that path, so the next edit to the list fails instead of merging quietly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: acd9417a0a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…cline The rule that a declined announce keeps its path was spread across five sibling branches of `start_announce`, each responsible for calling `declined` before returning. That is what let the merge lose one: nothing about a new early return makes the omission visible. Take the record once, immediately after the duplicate check, and let acceptance replace it. Every way out then leaves it standing by construction, and the next early return cannot free a path the peer still holds. `restart_announce` keeps `declined`, where the point is finishing a route that is already attached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`reserve` overwrites whatever is at the path, which is only safe because the duplicate check just established there is nothing there. Say so, and assert it, so a later caller cannot drop an attached route's source without finishing it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A regression that landed on
devwhen #3066 squashed onto a base that already carried #3065. Neither side conflicted, so nothing flagged it.What broke
#3065 made every path out of
start_announcethat declines an announce record it first, viaannounced.declined(path). That is the whole of the #3050 fix: a declined announce keeps its path, so a laterANNOUNCE_STARTfor it is reported rather than accepted, and the declined one'sANNOUNCE_ENDretires nothing instead of a stranger's route.#3066 added a decline path that #3065 never saw, where rewriting a Lite03 placeholder hop would name this session twice. It was written against a base without the record, so it just returns:
On
devtoday that leaves five ways to decline an announce and four that record it.git merge-treereports the merge clean, both branches compile, and every existing test passes, because the invariant lives across sibling call sites rather than in anything a merge can see.The fix
Record it like the other four. The mechanical half is one line.
The rest is
every_declined_announce_is_recorded, which walks the path that was missed: a chain the placeholder rewrite cannot take because this session's id is already in it, then a secondANNOUNCE_STARTfor the same path, which must beError::ProtocolViolation. Removing thedeclinedcall fails it with "a declined announce still holds its path, so a second start for it is a violation".That converts the rule from something a reviewer has to notice into something CI enforces, which is the actual point: the decline list is exactly the kind of thing the next edit falls off the end of, and the failure is silent.
Verification
just checkclean; 3277 Rust tests and 590@moq/nettests pass.cargo check -p moq-ffi -p libmoqclean too, sincejust checknever builds those.🤖 Generated with Claude Code
(written by Claude Opus 5)