feat(transport): share one response-tag composer and add a targeted response sender - #109
Merged
ContextVM-org merged 1 commit intoAug 19, 2026
Conversation
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.
Part of #100
Sixth piece of CEP-8, after the client negotiation in #108. No payments code in it: this is the transport primitive PR 8's gating middleware needs, plus a refactor that had to land before it.
Two changes, in that order.
send_responseand the CEP-41 deferred publish each wrote out the same outbound tag policy by hand, so that's now one function both call. Thensend_targeted_response, which publishes to a specific client and request event without consuming the correlation route, so a middleware can answer a request without ending it.What's here
compose_response_tags: routing, CEP-35 discovery replay, CEP-8 effective-mode disclosure,cappricing. Private, synchronous, takes values the caller already resolved under its own session lock.send_responseandpublish_open_stream_deferred_responserouted through it.send_response'stagsbinding is now immutable, which turns "append before you measure" from a comment into a compile error.send_targeted_response, plustargeted_response_senderreturning the same publish as a closure for callers with no&self.TargetedResponseSender, modeled on the existingPublishFramealias.transport_integration.rs, 1 end to end through a real client.No
Cargo.tomlchange, no new dependency, no FFI change.Why it's built this way
send_responseand the deferred publish are the two copies that have to stay identical by hand, and all four of PR 4's revision rounds were that hand-sync failing. The targeted sender would have been the third copy. Doing it first was the deal when it got deferred.mainin a worktree, published the same responses through both, diffed the events. Byte identical on both paths across plain, first-response discovery, the advertisement-collision case, a capability-list result, and oversized. That last one carries the most risk: the start frame keeps the full composed list and every continuation frame keeps onlypande.event_routesalone. The sender has to be callable by a caller that answers without dropping the request. Consume the route there and the real response later fails correlation and the client hangs. The seam already pops the route when a middleware drops, so a gated request's route still gets released.p/e, so a-32042carrying the disclosure but no discovery set makes a stateless client latch a thin baseline and report no server name for the rest of the session. That's PR 4's round-2 bug on a new path. ts lands in the same place throughbuildOutboundTags.Heads up
The closure captures the announcement tag sets when you build it, not when you call it. Build it after setting those tags, or every targeted response goes out with empty discovery and empty pricing and nothing complains at compile time. There's a test pinning the difference and a tracker line for PR 9.
Three deliberate differences from ts. We pass pricing through the same shape gate
routeTargetedskips, which is unobservable in practice since the gate only fires on a result and every gating message is an error. We mirror the inbound gift-wrap kind whererouteTargeteddoesn't, so an ephemeral request doesn't get answered with a relay-stored wrap; ts disagrees with itself here becauseroute()does mirror, and it's worth raising upstream. And the response carries the client's original JSON-RPC id where ts carries the event id, because the two SDKs rewrite on opposite sides of their middleware seam. Both clients correlate on theetag, so neither is misled.A targeted response is never fragmented, matching
routeTargeted, which has no oversized branch either. Filed as a follow-up next to the identical gap on the CEP-41 deferred path.Scope is
transportand notpaymentsbecause nothing here is payments-specific. The changelog entry still sits in the CEP-8 group.Gate is green: fmt, clippy
-D warnings, 847 tests under--all-featuresand 685 under--no-default-features, doc, the FFI C suite against an empty diff, MSRV 1.88, plus CEP-22, CEP-41 ande2e_happy_pathby name. 23 mutations cut at the composer, at all three callers and at both sender forms, all killed.