gRPC docs audit: staleness fixes, sample startup regression, findings report#3461
Closed
erikshafer wants to merge 3 commits into
Closed
gRPC docs audit: staleness fixes, sample startup regression, findings report#3461erikshafer wants to merge 3 commits into
erikshafer wants to merge 3 commits into
Conversation
Adds the fourth canonical gRPC RPC shape (stream TRequest -> TResponse) to the proto-first codegen path, backed by a new core streaming-input primitive: - IMessageBus.InvokeStreamAsync<TRequest, TResponse>(IAsyncEnumerable<TRequest>) dispatches a whole inbound stream to one handler invocation keyed on the closed IAsyncEnumerable<TRequest> message type, awaiting a single response. Local invocation only; reuses the existing Executor.InvokeAsync path. - GrpcServiceChain drops the client-streaming fail-fast, classifies onto a new ClientStreamingMethods list, and emits ForwardClientStreamToMessageBusFrame. WolverineGrpcStreamAdapters.ReadAllAsync bridges IAsyncStreamReader<T> to IAsyncEnumerable<T> (the stock extension collides across Grpc.Core.Api and Grpc.Net.Common). - The server exception interceptor now covers client-streaming handlers, and IGrpcEndpointManifest surfaces GrpcRpcStreamKind.ClientStreaming. - GreeterProtoFirstGrpc gains a CollectGreetings RPC (plus the runtime compilation reference the sample lost in the 6.0 Roslyn decoupling), docs updated across the gRPC guide, and CHANGELOG gets its first gRPC entries. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Conform the streaming-request primitive to the existing StreamAsync<T> overload family rather than introducing a new method-name family. Arity disambiguates the two directions (one type argument streams responses out, two stream requests in), and the exception interceptor comment that originally deferred client streaming spoke of "the matching IMessageBus overloads" - this restores that recorded intent. Test file/class/method names follow suit (streaming_request_support). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…decoupling Documentation comb-through of every gRPC surface (guide pages, sample READMEs, secondary mentions), verifying each claim against source: - Eight gRPC sample servers crashed at startup with 'No IAssemblyGenerator is registered' since the 6.0 runtime-codegen decoupling; add the Wolverine.RuntimeCompilation ProjectReference (mirroring the fix GreeterProtoFirstGrpc/Server got in JasperFx#3459). All nine servers boot. - samples.md + RacerWithGrpc README still said StreamAsync<TRequest, TResponse> hadn't landed; it has, as the client-streaming primitive, not a bidi loop. Reworded both. - errors.md now states which call shapes the exception interceptor covers (unary, server-, client-streaming; bidi deferred). - contracts.md/handlers.md claimed hand-written delegation wrappers are named {ClassName}GrpcHandler; the GrpcService suffix is stripped first (HandWrittenGrpcServiceChain.ResolveTypeName). - guide/samples.md listed only six of the eight gRPC samples; added GreeterCodeFirstGrpc and ProgressTrackerWithGrpc rows. - handlers.md codegen-preview identifier list matched to the CLI. Wolverine.Grpc.Tests 281/281, full wolverine.slnx Release build clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This pull request completes a broad gRPC documentation audit while also carrying the underlying runtime changes (from the dependent branch) needed to make proto-first client-streaming workable end-to-end: a new IMessageBus.StreamAsync<TRequest, TResponse> primitive, proto-first wrapper codegen updates, endpoint manifest updates, expanded test coverage, and sample startup fixes.
Changes:
- Add streaming-requests support to the core bus via
StreamAsync<TRequest, TResponse>(IAsyncEnumerable<TRequest> …), plus tests and test harness updates. - Add proto-first gRPC client-streaming wrapper generation (adapter + interceptor + manifest surfacing), plus new end-to-end gRPC tests.
- Fix gRPC docs staleness/contradictions and repair gRPC sample server startup by adding the runtime-compilation module reference.
Reviewed changes
Copilot reviewed 47 out of 47 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Wolverine/Transports/Sending/SendingEnvelopeLifecycle.cs | Plumbs new streaming-request overloads through lifecycle wrapper. |
| src/Wolverine/TestMessageContext.cs | Adds test bus support for streaming-request overloads. |
| src/Wolverine/Runtime/MessageBus.cs | Implements StreamAsync<TRequest, TResponse> dispatch for local stream handlers. |
| src/Wolverine/IMessageBus.cs | Extends ICommandBus with streaming-request overloads + XML docs. |
| src/Wolverine/Configuration/GrpcEndpointManifest.cs | Extends stream-kind enum/docs to include client streaming. |
| src/Wolverine.Grpc/WolverineGrpcStreamAdapters.cs | Adds IAsyncStreamReader<T> → IAsyncEnumerable<T> adapter for generated wrappers. |
| src/Wolverine.Grpc/WolverineGrpcExceptionInterceptor.cs | Intercepts/translate exceptions for client-streaming RPCs. |
| src/Wolverine.Grpc/GrpcServiceChain.cs | Adds client-streaming method discovery + wrapper frame emission. |
| src/Wolverine.Grpc/GrpcEndpointManifest.cs | Surfaces proto-first client-streaming endpoints in manifest descriptors. |
| src/Wolverine.Grpc.Tests/Wolverine.Grpc.Tests.csproj | Adds new proto file for client-streaming test service. |
| src/Wolverine.Grpc.Tests/ProtoFirst/proto_first_grpc_tests.cs | Adds client-streaming roundtrip + classification assertions. |
| src/Wolverine.Grpc.Tests/GrpcClientStreaming/Protos/grpc_client_stream_test.proto | New proto contract for client-streaming E2E tests. |
| src/Wolverine.Grpc.Tests/GrpcClientStreaming/grpc_client_streaming_tests.cs | New E2E tests validating client-streaming wrapper behavior. |
| src/Wolverine.Grpc.Tests/GrpcClientStreaming/CollectStub.cs | New proto-first stub used for client-streaming tests. |
| src/Wolverine.Grpc.Tests/GrpcClientStreaming/CollectHandler.cs | New handler that folds inbound stream into a single reply. |
| src/Wolverine.Grpc.Tests/GrpcClientStreaming/ClientStreamingFixture.cs | New in-process gRPC host fixture for client-streaming tests. |
| src/Wolverine.Grpc.Tests/GrpcBidiStreaming/Protos/grpc_bidi_test.proto | Updates commentary now that client streaming is supported. |
| src/Wolverine.Grpc.Tests/GrpcBidiStreaming/grpc_bidi_streaming_tests.cs | Inverts former “fail fast” tests to “construction succeeds” tests. |
| src/Wolverine.Grpc.Tests/grpc_endpoint_manifest_3265.cs | Extends endpoint-manifest assertions for client-streaming endpoints. |
| src/Wolverine.Grpc.Tests/grpc_capabilities_descriptor_source_3267.cs | Extends capabilities assertions to include client-streaming origins. |
| src/Testing/CoreTests/TestMessageContextTests.cs | Adds tests asserting TestMessageContext records stream invocations. |
| src/Testing/CoreTests/Acceptance/streaming_request_support.cs | New acceptance tests for stream-request semantics (cancel, options, cascading, etc.). |
| src/Samples/RacerWithGrpc/README.md | Updates sample explanation re: bidi vs client-streaming primitive. |
| src/Samples/RacerWithGrpc/RacerServer/RacerServer.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/ProgressTrackerWithGrpc/Server/Server.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/PingPongWithGrpcStreaming/Ponger/Ponger.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/PingPongWithGrpc/Ponger/Ponger.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/OrderChainWithGrpc/OrderServer/OrderServer.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/OrderChainWithGrpc/InventoryServer/InventoryServer.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/GreeterWithGrpcErrors/Server/Server.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/GreeterProtoFirstGrpc/Server/Server.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| src/Samples/GreeterProtoFirstGrpc/Server/GreeterHandler.cs | Adds client-streaming handler that folds requests into one reply. |
| src/Samples/GreeterProtoFirstGrpc/Server/GreeterGrpcService.cs | Updates doc comment to reflect new RPC shape coverage. |
| src/Samples/GreeterProtoFirstGrpc/README.md | Documents client-streaming demo in sample output + explanation. |
| src/Samples/GreeterProtoFirstGrpc/Messages/Protos/greeter.proto | Adds CollectGreetings client-streaming RPC + reply message. |
| src/Samples/GreeterProtoFirstGrpc/Client/Program.cs | Adds client-streaming invocation example. |
| src/Samples/GreeterCodeFirstGrpc/Server/Server.csproj | Adds RuntimeCompilation reference to fix sample startup. |
| docs/guide/samples.md | Adds missing gRPC samples to the main samples index list. |
| docs/guide/messaging/message-bus.md | Adds “Streaming Requests” docs for new bus primitive. |
| docs/guide/grpc/streaming.md | Updates streaming guide for client-streaming support + limitations. |
| docs/guide/grpc/samples.md | Updates Greeter/Racer docs to reflect client-streaming primitive semantics. |
| docs/guide/grpc/multi-tenancy.md | Updates multi-tenancy notes for proto-first client-streaming coverage. |
| docs/guide/grpc/index.md | Updates gRPC landing page for client-streaming support and limitations. |
| docs/guide/grpc/handlers.md | Fixes wrapper naming + CLI identifier docs + Validate applicability. |
| docs/guide/grpc/errors.md | Clarifies exception-interceptor coverage for client streaming (not bidi). |
| docs/guide/grpc/contracts.md | Updates limitations + wrapper naming + adds client-streaming section. |
| CHANGELOG.md | Adds unreleased entries describing the new streaming-requests primitive and gRPC client-streaming codegen. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+251
to
+254
| throw new NotSupportedException( | ||
| $"StreamAsync is only supported for locally-handled message streams, and no handler accepts {messageType.FullNameInCode()} as its message type. " + | ||
| $"Define a handler like 'Task<TResponse> Handle(IAsyncEnumerable<{typeof(TRequest).FullNameInCode()}> messages, CancellationToken token)'."); | ||
| } |
Comment on lines
+240
to
+247
| Task<TResponse> ICommandBus.StreamAsync<TRequest, TResponse>(IAsyncEnumerable<TRequest> messages, | ||
| CancellationToken cancellation, TimeSpan? timeout) | ||
| { | ||
| _invoked.Add(messages); | ||
|
|
||
| var response = findResponse<TResponse>(messages); | ||
| return Task.FromResult(response); | ||
| } |
Comment on lines
+249
to
+259
| Task<TResponse> ICommandBus.StreamAsync<TRequest, TResponse>(IAsyncEnumerable<TRequest> messages, | ||
| DeliveryOptions options, CancellationToken cancellation, TimeSpan? timeout) | ||
| { | ||
| var envelope = new Envelope(messages); | ||
| options.Override(envelope); | ||
|
|
||
| _invoked.Add(envelope); | ||
|
|
||
| var response = findResponse<TResponse>(messages); | ||
| return Task.FromResult(response); | ||
| } |
Contributor
Author
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.
Full comb-through of every gRPC documentation surface — the nine guide pages, the sample READMEs, and every secondary page that mentions gRPC — checking three failure classes: staleness against source, broken links/anchors/snippets, and cross-page contradictions. Ground truth was always source, verified with file:line before touching anything.
Fixed
GreeterProtoFirstGrpc/Server(fixed in Proto-first gRPC client streaming via new StreamAsync<TRequest, TResponse> overload #3459) threwInvalidOperationException: No IAssemblyGenerator is registeredatMapWolverineGrpcServices(). Reproduced onPingPongWithGrpc/Ponger, then added theWolverine.RuntimeCompilationProjectReference(mirroring the Proto-first gRPC client streaming via new StreamAsync<TRequest, TResponse> overload #3459 fix, comment included) to all eight:PingPongWithGrpc/Ponger,PingPongWithGrpcStreaming/Ponger,GreeterCodeFirstGrpc/Server,RacerWithGrpc/RacerServer,GreeterWithGrpcErrors/Server,ProgressTrackerWithGrpc/Server,OrderChainWithGrpc/OrderServer,OrderChainWithGrpc/InventoryServer. All nine servers now boot and reach "Now listening on" (each verified individually via its README'sdotnet run … --framework net9.0command).docs/guide/grpc/samples.md:253(RacerWithGrpc): "It's the path to bidi today untilIMessageBus.StreamAsync<TRequest, TResponse>lands" — that API landed in Proto-first gRPC client streaming via new StreamAsync<TRequest, TResponse> overload #3459, but as the client-streaming primitive (stream of requests → single response), not a bidi shape. Reworded to say the per-item bridge is still the bidi path and why.src/Samples/RacerWithGrpc/README.md:10: same staleness — "Bidi 'just works' without Wolverine accepting an inboundIAsyncEnumerableon the bus" was written before the bus did accept one. Reworded: no bidi-shaped primitive exists;StreamAsync<TRequest, TResponse>folds the inbound stream into a single response.docs/guide/grpc/errors.md:17: the page never said which server call shapes the exception interceptor covers, and coverage is now shape-dependent. Added one sentence: unary, server-streaming, and client-streaming are intercepted; bidi is deliberately deferred (matchesWolverineGrpcExceptionInterceptor.cs:36-40— only those three handler overrides exist).docs/guide/samples.md: listed only six of the eight gRPC samples whiledocs/guide/grpc/index.md:84andsamples.md:3say "eight". Added the missingGreeterCodeFirstGrpcandProgressTrackerWithGrpcrows in the same style/order as the gRPC samples page.docs/guide/grpc/contracts.md:152+docs/guide/grpc/handlers.md:81: both claimed the hand-written delegation wrapper is named{ClassName}GrpcHandler. Actual convention strips aGrpcServicesuffix first:PingGrpcService→PingGrpcHandler(HandWrittenGrpcServiceChain.ResolveTypeName,HandWrittenGrpcServiceChain.cs:282-288). Corrected both.docs/guide/grpc/handlers.md:106: describedcodegen-preview --grpc's accepted identifiers as "(bare proto service name, stub class name, or short-galias)". The third accepted identifier is the generated wrapper name (e.g.GreeterGrpcHandler);-gis the flag alias, not an identifier (WolverineDiagnosticsCommand.cs:42-47). Corrected.Verified clean
Claims vs code (file:line checked):
client.md:76— client propagation interceptor covers every call shape: confirmed, all five overrides present (WolverineGrpcClientPropagationInterceptor.cs:44-80: blocking unary, async unary, server-streaming, client-streaming, duplex).client.mdheader table, never-overwrite rule, no-IMessageContextno-op, per-clientPropagateEnvelopeHeaders— all matchWolverineGrpcClientPropagationInterceptor.cs:100-146andWolverineGrpcClientOptions.cs.WolverineGrpcServicePropagationInterceptor.cs:109-118reads exactlycorrelation-id+tenant-id, as documented.index.mdAPI Reference table — every listed type/member exists under that exact name with matching semantics:AddWolverineGrpc/MapWolverineGrpcServices(WolverineGrpcExtensions.cs:42,54,136),WolverineGrpcServiceBase.Bus,AddMiddleware<T>(filter?)/AddPolicy/MapException<T>(WolverineGrpcOptions.cs:93-172),IGrpcChainPolicy.Apply(protoFirst, codeFirst, handWritten, rules, container)(IGrpcChainPolicy.cs:29-34), all threeModify*ChainAttributefiles,WolverineGrpcExceptionMapper.Map/.MapToException(WolverineGrpcExceptionMapper.cs:27,88),UseGrpcRichErrorDetailsonWolverineOptions(GrpcRichErrorDetailsExtensions.cs:37),UseFluentValidationGrpcErrorDetailsinWolverine.FluentValidation.Grpc,IGrpcStatusDetailsProvider,IValidationFailureAdapter. Same forclient.md's API table (WolverineGrpcClientBuilder.cs,WolverineGrpcClientKind).errors.mdexception table matchesWolverineGrpcExceptionMapper.Mapcase-for-case; precedence claim (rich details → userMapException→ default table) matchesWolverineGrpcExceptionInterceptor.Translate; inheritance-respected/last-wins matchesWolverineGrpcOptions.TryMapException:180-198; client-side inverse table matchesMapToException:88-106including theUnauthenticated → UnauthorizedAccessExceptionrow.handlers.md— the four discovery passes matchMapWolverineGrpcServices/MapGeneratedServices(WolverineGrpcExtensions.cs:136-235);grpc.AddMiddleware<T>()really does reach all three chain kinds (GrpcGraph.cs:157-159;CodeFirstGrpcServiceChain.cs:215,279clones global befores/afters per method); Validate rules confirmed —Status?nullable required (HandWrittenGrpcServiceChain.cs:211), per-request-type matching (:195), not woven for bidi (GrpcServiceChain.cs:631) or client-streaming (:682).contracts.md— code-first generated namingIGreeterCodeFirstService→GreeterCodeFirstServiceGrpcHandler(CodeFirstGrpcServiceChain.cs:368); "silently skipped" forIAsyncEnumerable<TRequest>-parameter interface methods (:373); proto-first{ProtoServiceName}GrpcHandler(GrpcServiceChain.cs:87).multi-tenancy.md— strategy namesIsRequestHeaderValue/IsClaimTypeNamed/DefaultIs/AssertExists/DetectWithall exist (GrpcTenantIdDetection.cs:41-66); the exactAssertExistsfailure message matches (GrpcTenantDetection.cs:13); zero-config default gated onPropagateEnvelopeHeaders(WolverineGrpcOptions.cs:37-43); code-first server-streaming limitation (CodeFirstGrpcServiceChain.cs:133,198); Wolverine.Grpc: server-side tenant-id detection (mirror of Wolverine.Http's ITenantDetectionPolicies) #3368 exists (closed, matching title).streaming.md— all four shape statements consistent withindex.md/contracts.md; exception-interceptor shape coverage; empty-stream and incremental-consumption claims matchIMessageBus.cs:140-167xml-docs andmessage-bus.md.message-bus.mdStreaming Responses/Requests — overload family matchesIMessageBus.cs:125-167(arity story,DeliveryOptionsoverloads); fail-fastNotSupportedExceptionnaming the expected signature is real (MessageBus.cs:249-254).command-line.md:161-203,tutorials/command-line-diagnostics.md:78,198) —wolverine-diagnostics codegen-preview --grpcwith-galias and all three identifier forms matchWolverineDiagnosticsCommand.cs.durability/efcore/transactional-middleware.md:401gRPC mention — accurate.Links, anchors, snippets:
config.mts:294-302) lists all nine pages, links match filenames.#anchorin the nine gRPC pages resolves (headings exist; VitePress slugification checked, incl.#http-endpoint-message-handler-combo,#streaming-responses,#streaming-requests,#client-streaming-proto-first,#timeout-messages). Note:vitepress buildpasses butignoreDeadLinks: trueis set (config.mts:378), so the build proves nothing about links — all verification here was manual.github.com/JasperFx/wolverine/tree|blob/main/...URL points at a path that exists on JasperFxmain(all eight sample dirs,EnvelopeConstants.cs, all snippet sources).sagas.md×4,samples.md×1) — region markers exist at exactly the#Lranges in the links (10-37, 15-55, 12-29, 35-60, 28-37) and the expanded blocks match current source.samples.md(#greeter,#counter,#coder,#racer,#progressor) exist in the upstream examples README (fetched live).Samples/READMEs:
PingPongWithGrpc,PingPongWithGrpcStreaming,GreeterProtoFirstGrpc,GreeterWithGrpcErrors,OrderChainWithGrpcREADMEs accurate (project layouts, run commands incl.--framework net9.0, described mechanisms match code);GreeterCodeFirstGrpcandProgressTrackerWithGrpchave no README (docs never claim one).Gates:
Wolverine.Grpc.Tests281/281 passed;dotnet build wolverine.slnx -c Releaseclean (0 warnings, 0 errors).Flagged, not fixed
docs/guide/grpc/sagas.mdis stale on this branch but already fixed onmain. The local copy still says a header-identified saga fails withIndeterminateSagaStateIdExceptionsurfaced asStatusCode.Internal, "tracked in gRPC service path doesn't propagate saga-id — header-identified sagas can't be started/continued over gRPC #3385" — Actionable diagnostic for a header-identified saga over gRPC (GH-3385) #3403 (merged Jul 13) changed it to an actionableInvalidArgumentdiagnostic, closed gRPC service path doesn't propagate saga-id — header-identified sagas can't be started/continued over gRPC #3385, and updated the page onmain. Neither Proto-first gRPC client streaming via new StreamAsync<TRequest, TResponse> overload #3459 nor this branch touchessagas.md, somain's corrected version survives the merges untouched; editing it here would only manufacture a conflict.IGrpcChainPolicy.cs:15-16remarks say code-first chains "do not yet participate in theChain<>middleware pipeline (P3)" — they do now;WolverineGrpcOptions.cs:132-133says the defaultAddMiddlewarefilter matches "every proto-first and hand-written gRPC chain" —IsGrpcChainmatches all three kinds.dotnet mdsnippetsunavailable, no repo tool manifest) — snippet verification was done by hand (regions + line ranges + content diff) rather than by regeneration.🤖 Generated with Claude Code