Skip to content

gRPC docs audit: staleness fixes, sample startup regression, findings report#3461

Closed
erikshafer wants to merge 3 commits into
JasperFx:mainfrom
erikshafer:grpc-docs-audit
Closed

gRPC docs audit: staleness fixes, sample startup regression, findings report#3461
erikshafer wants to merge 3 commits into
JasperFx:mainfrom
erikshafer:grpc-docs-audit

Conversation

@erikshafer

Copy link
Copy Markdown
Contributor

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.

Branch note: based on grpc-client-streaming-codegen (PR #3459, still open), because the audit depends on that PR's docs edits. Until #3459 merges, this PR's diff includes its commits; after it merges, only the audit delta (18392a8ea) remains. Merge #3459 first.

Fixed

  1. Eight gRPC sample servers crashed at startup (pre-existing 6.0 regression, not introduced by any docs PR). Since the Roslyn/runtime-codegen decoupling, every documented gRPC sample server except GreeterProtoFirstGrpc/Server (fixed in Proto-first gRPC client streaming via new StreamAsync<TRequest, TResponse> overload #3459) threw InvalidOperationException: No IAssemblyGenerator is registered at MapWolverineGrpcServices(). Reproduced on PingPongWithGrpc/Ponger, then added the Wolverine.RuntimeCompilation ProjectReference (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's dotnet run … --framework net9.0 command).
  2. docs/guide/grpc/samples.md:253 (RacerWithGrpc): "It's the path to bidi today until IMessageBus.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.
  3. src/Samples/RacerWithGrpc/README.md:10: same staleness — "Bidi 'just works' without Wolverine accepting an inbound IAsyncEnumerable on 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.
  4. 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 (matches WolverineGrpcExceptionInterceptor.cs:36-40 — only those three handler overrides exist).
  5. docs/guide/samples.md: listed only six of the eight gRPC samples while docs/guide/grpc/index.md:84 and samples.md:3 say "eight". Added the missing GreeterCodeFirstGrpc and ProgressTrackerWithGrpc rows in the same style/order as the gRPC samples page.
  6. 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 a GrpcService suffix first: PingGrpcServicePingGrpcHandler (HandWrittenGrpcServiceChain.ResolveTypeName, HandWrittenGrpcServiceChain.cs:282-288). Corrected both.
  7. docs/guide/grpc/handlers.md:106: described codegen-preview --grpc's accepted identifiers as "(bare proto service name, stub class name, or short -g alias)". The third accepted identifier is the generated wrapper name (e.g. GreeterGrpcHandler); -g is 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.md header table, never-overwrite rule, no-IMessageContext no-op, per-client PropagateEnvelopeHeaders — all match WolverineGrpcClientPropagationInterceptor.cs:100-146 and WolverineGrpcClientOptions.cs.
  • Server-side round-trip claims — WolverineGrpcServicePropagationInterceptor.cs:109-118 reads exactly correlation-id + tenant-id, as documented.
  • index.md API 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 three Modify*ChainAttribute files, WolverineGrpcExceptionMapper.Map/.MapToException (WolverineGrpcExceptionMapper.cs:27,88), UseGrpcRichErrorDetails on WolverineOptions (GrpcRichErrorDetailsExtensions.cs:37), UseFluentValidationGrpcErrorDetails in Wolverine.FluentValidation.Grpc, IGrpcStatusDetailsProvider, IValidationFailureAdapter. Same for client.md's API table (WolverineGrpcClientBuilder.cs, WolverineGrpcClientKind).
  • errors.md exception table matches WolverineGrpcExceptionMapper.Map case-for-case; precedence claim (rich details → user MapException → default table) matches WolverineGrpcExceptionInterceptor.Translate; inheritance-respected/last-wins matches WolverineGrpcOptions.TryMapException:180-198; client-side inverse table matches MapToException:88-106 including the Unauthenticated → UnauthorizedAccessException row.
  • handlers.md — the four discovery passes match MapWolverineGrpcServices/MapGeneratedServices (WolverineGrpcExtensions.cs:136-235); grpc.AddMiddleware<T>() really does reach all three chain kinds (GrpcGraph.cs:157-159; CodeFirstGrpcServiceChain.cs:215,279 clones 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 naming IGreeterCodeFirstServiceGreeterCodeFirstServiceGrpcHandler (CodeFirstGrpcServiceChain.cs:368); "silently skipped" for IAsyncEnumerable<TRequest>-parameter interface methods (:373); proto-first {ProtoServiceName}GrpcHandler (GrpcServiceChain.cs:87).
  • multi-tenancy.md — strategy names IsRequestHeaderValue/IsClaimTypeNamed/DefaultIs/AssertExists/DetectWith all exist (GrpcTenantIdDetection.cs:41-66); the exact AssertExists failure message matches (GrpcTenantDetection.cs:13); zero-config default gated on PropagateEnvelopeHeaders (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 with index.md/contracts.md; exception-interceptor shape coverage; empty-stream and incremental-consumption claims match IMessageBus.cs:140-167 xml-docs and message-bus.md.
  • message-bus.md Streaming Responses/Requests — overload family matches IMessageBus.cs:125-167 (arity story, DeliveryOptions overloads); fail-fast NotSupportedException naming the expected signature is real (MessageBus.cs:249-254).
  • CLI docs (command-line.md:161-203, tutorials/command-line-diagnostics.md:78,198) — wolverine-diagnostics codegen-preview --grpc with -g alias and all three identifier forms match WolverineDiagnosticsCommand.cs.
  • durability/efcore/transactional-middleware.md:401 gRPC mention — accurate.

Links, anchors, snippets:

  • Sidebar (config.mts:294-302) lists all nine pages, links match filenames.
  • Every internal cross-page link and #anchor in 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 build passes but ignoreDeadLinks: true is set (config.mts:378), so the build proves nothing about links — all verification here was manual.
  • Every github.com/JasperFx/wolverine/tree|blob/main/... URL points at a path that exists on JasperFx main (all eight sample dirs, EnvelopeConstants.cs, all snippet sources).
  • All five MarkdownSnippets blocks (sagas.md ×4, samples.md ×1) — region markers exist at exactly the #L ranges in the links (10-37, 15-55, 12-29, 35-60, 28-37) and the expanded blocks match current source.
  • All five grpc-dotnet example anchors in samples.md (#greeter, #counter, #coder, #racer, #progressor) exist in the upstream examples README (fetched live).

Samples/READMEs: PingPongWithGrpc, PingPongWithGrpcStreaming, GreeterProtoFirstGrpc, GreeterWithGrpcErrors, OrderChainWithGrpc READMEs accurate (project layouts, run commands incl. --framework net9.0, described mechanisms match code); GreeterCodeFirstGrpc and ProgressTrackerWithGrpc have no README (docs never claim one).

Gates: Wolverine.Grpc.Tests 281/281 passed; dotnet build wolverine.slnx -c Release clean (0 warnings, 0 errors).

Flagged, not fixed

🤖 Generated with Claude Code

erikshafer and others added 3 commits July 17, 2026 21:20
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>
Copilot AI review requested due to automatic review settings July 18, 2026 18:34

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 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);
}
@erikshafer

Copy link
Copy Markdown
Contributor Author

Superseded: the audit commit (18392a8) has been fast-forwarded onto grpc-client-streaming-codegen and now ships with #3459 — this PR's diff became identical to that one. The full audit findings report (Fixed / Verified clean / Flagged) remains in this PR's description for reference.

@erikshafer erikshafer closed this Jul 18, 2026
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.

gRPC service path doesn't propagate saga-id — header-identified sagas can't be started/continued over gRPC

2 participants