Skip to content

gRPC: server-side envelope propagation + correct false docs/sample claims (GH-3368)#3369

Open
erikshafer wants to merge 1 commit into
JasperFx:mainfrom
erikshafer:grpc-server-side-tenant-propagation
Open

gRPC: server-side envelope propagation + correct false docs/sample claims (GH-3368)#3369
erikshafer wants to merge 1 commit into
JasperFx:mainfrom
erikshafer:grpc-server-side-tenant-propagation

Conversation

@erikshafer

Copy link
Copy Markdown
Contributor

Summary

  • Wolverine.Grpc's client interceptor already stamped correlation-id/tenant-id onto outgoing calls, but nothing on the server side read them back — despite docs/guide/grpc/client.md and the OrderChainWithGrpc sample's XML comments claiming a WolverineGrpcServicePropagationInterceptor already existed and did exactly this. It didn't; grepping src/Wolverine.Grpc confirmed AddWolverineGrpc() only ever registered the exception interceptor.
  • Adds the real WolverineGrpcServicePropagationInterceptor (registered automatically by AddWolverineGrpc(), positioned inside the exception interceptor to mirror the client-side ordering). It reads correlation-id/tenant-id off inbound gRPC metadata and applies them to the ambient IMessageContext unconditionally when present — matching the convention every other Wolverine transport uses on a freshly received envelope (MessageContext.ReadEnvelope), rather than a "set only if empty" guard (a fresh IMessageContext.CorrelationId is never actually empty — it's pre-seeded from Activity.Current — so that guard would have silently no-op'd, and an earlier draft of this change caught exactly that bug via its own tests).
  • Adds WolverineGrpcOptions.PropagateEnvelopeHeaders (default true) as the server-side off-switch, mirroring the existing client-side option.
  • parent-id/conversation-id are deliberately not propagated this way — they live on an Envelope, and there is no envelope yet at the point this interceptor runs (IMessageContext.Envelope is null outside of message handling). Cross-process parent/trace correlation for gRPC hops already happens separately via OpenTelemetry Activity propagation over HTTP/2.
  • Corrects the false claims: docs/guide/grpc/client.md and the OrderChainWithGrpc sample's ReserveStockHandler.cs XML comment now describe what's actually implemented instead of a type that never existed.

Related to #3368 (the original Discord question was about tenant-id detection for external gRPC callers; this closes the Wolverine-to-Wolverine hop half of that gap and fixes the docs/sample along the way). External-caller detection strategies — analogous to Wolverine.Http's ITenantDetectionPolicies (route/claim/subdomain-based) — are still open in #3368.

Test plan

  • dotnet build wolverine.slnx -c Release — full solution, 0 errors/warnings
  • dotnet test src/Wolverine.Grpc.Tests/Wolverine.Grpc.Tests.csproj — 255/255 passing, including 8 new tests:
    • Full Wolverine-to-Wolverine hop: client stamps headers → server interceptor reads them → invoked handler sees them via IMessageContext → echoed back (proves the actual bug the docs/sample claimed was already fixed)
    • Tenant-id propagates independent of correlation-id
    • An arbitrary non-Wolverine caller manually setting the raw header also works (the original Discord scenario)
    • PropagateEnvelopeHeaders = false actually disables it, verified against a dedicated in-process host
    • DI-registration idempotency guards for the new interceptor (repeat AddWolverineGrpc() calls don't double-stack it) + interceptor ordering assertion (exception interceptor stays outermost)
  • Verified the OrderChainWithGrpc sample separately does not actually run today (Dynamic codegen mode throws — the sample projects don't reference Wolverine.RuntimeCompilation), confirming it was never actually executed/verified before — a pre-existing, unrelated issue left out of scope here. The in-process test suite exercises the identical code path (real Kestrel TestServer, real interceptors, real Bus.InvokeAsync).

🤖 Generated with Claude Code

…le claims (JasperFxGH-3368)

Wolverine.Grpc's client interceptor stamped correlation-id/tenant-id onto outgoing
calls, but nothing on the server side read them back — despite docs and the
OrderChainWithGrpc sample claiming a WolverineGrpcServicePropagationInterceptor
already existed and did this. It didn't. Adds the real interceptor (registered by
AddWolverineGrpc(), off via WolverineGrpcOptions.PropagateEnvelopeHeaders), fixes
the false claims in docs/guide/grpc/client.md and the sample's XML comments, and
covers the fix with end-to-end tests through a real Kestrel TestServer + Bus.InvokeAsync.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 11, 2026 16:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR closes a gap in Wolverine.Grpc by adding server-side propagation of the correlation-id and tenant-id envelope headers from inbound gRPC metadata onto the ambient IMessageContext, and corrects documentation/sample text that previously claimed this behavior already existed.

Changes:

  • Added WolverineGrpcServicePropagationInterceptor to read inbound correlation-id/tenant-id metadata and apply them to IMessageContext (behind a new option).
  • Added WolverineGrpcOptions.PropagateEnvelopeHeaders (default true) and wired the interceptor into AddWolverineGrpc() with correct interceptor ordering.
  • Added end-to-end tests covering propagation behavior and the disable switch; updated docs and sample comments to match actual behavior.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Wolverine.Grpc/WolverineGrpcServicePropagationInterceptor.cs New server interceptor that copies inbound correlation-id/tenant-id metadata onto IMessageContext.
src/Wolverine.Grpc/WolverineGrpcOptions.cs Adds server-side PropagateEnvelopeHeaders option (default true).
src/Wolverine.Grpc/WolverineGrpcExtensions.cs Registers the new interceptor and ensures it runs inside (after) the exception interceptor.
src/Wolverine.Grpc.Tests/ServerPropagation/server_propagation_interceptor_tests.cs End-to-end tests proving the full hop (client stamps → server reads → handler sees).
src/Wolverine.Grpc.Tests/ServerPropagation/server_propagation_disabled_tests.cs Tests that server-side propagation can be disabled.
src/Wolverine.Grpc.Tests/ServerPropagation/PropagationEchoGrpcService.cs Test gRPC service forwarding to Bus.InvokeAsync(...).
src/Wolverine.Grpc.Tests/ServerPropagation/PropagationEchoContracts.cs Test gRPC contracts and DTOs for propagation scenarios.
src/Wolverine.Grpc.Tests/ServerPropagation/DisabledPropagationFixture.cs In-process test host fixture with propagation disabled.
src/Wolverine.Grpc.Tests/add_wolverine_grpc_idempotency_tests.cs Ensures interceptor registration idempotency and ordering.
src/Samples/OrderChainWithGrpc/InventoryServer/ReserveStockHandler.cs Fixes sample XML comments to reflect the now-real interceptor and clarify trace propagation.
docs/guide/grpc/client.md Corrects and expands docs to match actual server-side behavior and configuration.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants