Skip to content

gRPC saga coverage: message-identified sagas work like HTTP; header-identified gap pinned (refs #3385)#3386

Merged
jeremydmiller merged 3 commits into
JasperFx:mainfrom
erikshafer:grpc-saga-id-3385
Jul 12, 2026
Merged

gRPC saga coverage: message-identified sagas work like HTTP; header-identified gap pinned (refs #3385)#3386
jeremydmiller merged 3 commits into
JasperFx:mainfrom
erikshafer:grpc-saga-id-3385

Conversation

@erikshafer

@erikshafer erikshafer commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Refs #3385.

Adds gRPC saga tests that mirror the existing HTTP endpoint + saga coverage, proving sagas work over gRPC the same way they do over HTTP — and documenting the one case that doesn't (which HTTP shares). See the findings summary on #3385.

What's here

Happy path — message-identified sagas work over gRPC (green).
can_start_and_continue_a_message_identified_saga_over_grpc is the gRPC parallel of Wolverine.Http.Tests.building_a_saga_and_publishing_other_messages_from_http_endpoint: a saga whose id rides on the request DTO is started, persisted, continued, completed, and its state deleted — all through the same Bus.InvokeAsync pipeline a [WolverinePost] endpoint uses. Persistence is asserted via the in-memory saga persistor, so no database is required.

Characterization — header-identified sagas hit the same limitation HTTP has.
starting_a_header_identified_saga_over_grpc_fails_with_opaque_status_today pins the current behavior: a saga whose id is resolved from the envelope saga-id header can't resolve it over a gRPC service hop (nothing carries saga-id across), surfacing as an opaque StatusCode.Internal. When we land the scoped diagnostic (or full propagation) from #3385, this test's assertions flip.

Notes

🤖 Generated with Claude Code

…erFxGH-3385)

Pins current behavior: a saga whose id is resolved from the envelope
saga-id header (no id member on the message) cannot be started or
continued over a gRPC service hop, because saga-id is not propagated
by the client/server propagation interceptors nor seeded onto the
invoked envelope by Executor.InvokeAsync<T>. The RPC currently fails
with an opaque StatusCode.Internal.

Uses a standalone in-memory host (default in-memory saga persistence,
no database) modeled on the ServerPropagation test fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 12, 2026 19:32

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

Adds a new gRPC-focused characterization test suite to lock in the current behavior described in #3385: header-identified sagas (saga id resolved from the saga-id envelope header) fail across a gRPC hop because saga-id is not propagated, and the client sees an opaque StatusCode.Internal.

Changes:

  • Introduces a new SagaOverGrpc/ test fixture that hosts an in-process gRPC server and client over TestServer.
  • Adds code-first gRPC contracts + service that forwards requests to IMessageBus.InvokeAsync<TResponse>() to reproduce the saga behavior.
  • Adds a characterization test asserting the current opaque Internal failure mode when starting a header-identified saga over gRPC.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/Wolverine.Grpc.Tests/SagaOverGrpc/SagaOverGrpcFixture.cs New in-process gRPC host/channel fixture wired to just the saga handler + service.
src/Wolverine.Grpc.Tests/SagaOverGrpc/SagaOverGrpcContracts.cs Code-first gRPC contract and DTOs (intentionally id-less request to force envelope-based saga id resolution).
src/Wolverine.Grpc.Tests/SagaOverGrpc/saga_over_grpc_tests.cs Characterization test asserting current StatusCode.Internal + saga-id resolution failure detail.
src/Wolverine.Grpc.Tests/SagaOverGrpc/CountingSagaGrpcService.cs gRPC service shim that forwards to Wolverine InvokeAsync<T>() per docs.
src/Wolverine.Grpc.Tests/SagaOverGrpc/CountingSaga.cs Minimal header-identified saga state/handler used to trigger the failure path.

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

Comment on lines +1 to +5
using Grpc.Net.Client;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
erikshafer and others added 2 commits July 12, 2026 16:39
…asperFx#3386)

Matches the sibling ServerPropagation fixtures, which don't import it.
Verified: test project builds clean without it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JasperFxGH-3385)

Mirrors Wolverine.Http.Tests.building_a_saga_and_publishing_other_messages_
from_http_endpoint over a gRPC hop: a saga whose id rides on the request DTO
starts, persists, continues, and completes through the same Bus.InvokeAsync
pipeline an HTTP endpoint uses. Proves Jeremy's expectation that sagas "work
just like HTTP" over gRPC for the message-identified case.

Asserts persistence via the in-memory saga persistor (no database), the
in-memory equivalent of the HTTP test's Marten LoadAsync. Extends the existing
SagaOverGrpcFixture to host both the message-identified (ReservationSaga) and
header-identified (CountingSaga) scenarios.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@erikshafer erikshafer changed the title Characterization test: header-identified sagas over gRPC (refs #3385) gRPC saga coverage: message-identified sagas work like HTTP; header-identified gap pinned (refs #3385) Jul 12, 2026
@jeremydmiller
jeremydmiller merged commit e58cdbb into JasperFx:main Jul 12, 2026
24 checks passed

@jeremydmiller jeremydmiller left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Great no-regret groundwork — the in-memory persistor assertions keep this database-free, and the characterization test is exactly what we want pinned ahead of the GH-3385 decision. Merging.

jeremydmiller added a commit that referenced this pull request Jul 12, 2026
Adapts the HTTP + Sagas documentation for gRPC: same reservation example,
all samples as mdsnippets regions sourced from the Wolverine.Grpc.Tests
SagaOverGrpc suite merged in #3386. Documents message-carried saga identity
as the supported path and the header-identified limitation (GH-3385).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 13, 2026
A header-identified saga cannot work over a gRPC hop: the saga-id envelope
header is not propagated across the call, so no id can be resolved. That is
unchanged and is not what this fixes. What it fixes is the diagnostic.

The caller used to get StatusCode.Internal with "Could not determine a valid
saga state id for Envelope ..." -- true, but it names no cause and no remedy,
and the remedy is not guessable, because the reason the id is missing is a
property of the transport rather than of the message.

It now maps to StatusCode.InvalidArgument with a detail that says the identity
must be on the message body, why (the header does not cross the hop), how to
supply it ([SagaIdentity] or a matching property), and where to read more.
InvalidArgument rather than Internal is deliberate per AIP-193: the request
cannot succeed as sent and retrying will not change that -- it is a caller-side
contract problem, not a transient server fault.

The mapping lives in WolverineGrpcExceptionMapper, which only runs at the gRPC
boundary, so the core exception is untouched and non-gRPC users see no change.

Message-identified sagas over gRPC already work and are unaffected (covered by
#3386). Per @erikshafer's analysis on the issue, full three-point saga-id
propagation is deliberately NOT taken: HTTP does not exercise the header path
either, so "works like HTTP" is already satisfied, and the complexity is not
warranted until someone shows a concrete need.

Flips the #3386 characterization test, and updates the gRPC + Sagas docs page.
Full Wolverine.Grpc.Tests 273/273. Full wolverine.slnx Release build clean.

Closes #3385.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jeremydmiller added a commit that referenced this pull request Jul 13, 2026
#3403)

A header-identified saga cannot work over a gRPC hop: the saga-id envelope
header is not propagated across the call, so no id can be resolved. That is
unchanged and is not what this fixes. What it fixes is the diagnostic.

The caller used to get StatusCode.Internal with "Could not determine a valid
saga state id for Envelope ..." -- true, but it names no cause and no remedy,
and the remedy is not guessable, because the reason the id is missing is a
property of the transport rather than of the message.

It now maps to StatusCode.InvalidArgument with a detail that says the identity
must be on the message body, why (the header does not cross the hop), how to
supply it ([SagaIdentity] or a matching property), and where to read more.
InvalidArgument rather than Internal is deliberate per AIP-193: the request
cannot succeed as sent and retrying will not change that -- it is a caller-side
contract problem, not a transient server fault.

The mapping lives in WolverineGrpcExceptionMapper, which only runs at the gRPC
boundary, so the core exception is untouched and non-gRPC users see no change.

Message-identified sagas over gRPC already work and are unaffected (covered by
#3386). Per @erikshafer's analysis on the issue, full three-point saga-id
propagation is deliberately NOT taken: HTTP does not exercise the header path
either, so "works like HTTP" is already satisfied, and the complexity is not
warranted until someone shows a concrete need.

Flips the #3386 characterization test, and updates the gRPC + Sagas docs page.
Full Wolverine.Grpc.Tests 273/273. Full wolverine.slnx Release build clean.

Closes #3385.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants