Skip to content

Keep the inbox id of a durable envelope when a CloudEvent with a Guid id is read from its data - #5396

Merged
jeremydmiller merged 2 commits into
JasperFx:mainfrom
lahma:cloudevents-keep-inbox-id
Nov 6, 2026
Merged

jeremydmiller merged 2 commits into
JasperFx:mainfrom
lahma:cloudevents-keep-inbox-id

Conversation

@lahma

@lahma lahma commented Nov 6, 2026

Copy link
Copy Markdown
Contributor

Problem

CloudEventsMapper.mapIncoming overwrites envelope.Id with the CloudEvent id whenever that id parses as a Guid. GH-5364 added keepExistingId for ReadFromData, but only for the non-Guid branch.

Reachable path: a Kafka listener with CloudEvents interop, DefaultIncomingMessage<T>() and the durable inbox.

  1. KafkaListener sets envelope.MessageType ??= _messageTypeName (KafkaListener.cs:229), so the message type is already set when the receiver sees the envelope.
  2. DurableReceiver calls UnwrapEnvelopeIfNecessary only when MessageType is empty, so Unwrap is skipped and the envelope is stored in wolverine_incoming_envelopes under its transport id. WasPersistedInInbox becomes true.
  3. HandlerPipeline calls serializer.ReadFromData(...) -> mapIncoming(..., keepExistingId: true), and the Guid branch replaces envelope.Id with the CloudEvent id.
  4. Mark-handled, the scheduled retry and the move to dead letters key on envelope.Id and match no row. The row stays Incoming and is run again on recovery, a retry is not persisted, and a dead-lettered message stays in the inbox.

Fix

The Guid branch leaves envelope.Id alone when keepExistingId is set and the envelope WasPersistedInInbox. A non-persisted envelope still takes the CloudEvent id, and Unwrap (keepExistingId: false) is unchanged.

Tests

  • CoreTests: a persisted envelope keeps its id; a non-persisted envelope still takes a Guid CloudEvent id.
  • Wolverine.Kafka.Tests: Kafka + PostgreSQL inbox, CloudEvents interop + DefaultIncomingMessage + durable inbox. After the handler succeeds, the inbox row is not left Incoming.

Both new tests fail without the fix and pass with it. Without it the Kafka test reports statuses should not contain "Incoming" but was actually ["Incoming"].

Copilot AI balanced review requested due to automatic review settings November 6, 2026 07:11

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.

🟡 Changes recommended

The private test helper needs to be renamed to guidIdJson, with both callers updated, to match the existing camelCase naming convention.

1 open issue
What changed in this PR

Preserves durable inbox identifiers when Wolverine reads CloudEvents with GUID ids, keeping subsequent inbox updates tied to the stored envelope.

Changes:

  • Prevents GUID CloudEvent ids from replacing persisted envelope ids during ReadFromData.
  • Adds unit coverage for persisted and non-persisted envelopes.
  • Adds a Kafka/PostgreSQL regression test for CloudEvents with a default incoming message type.
File Description
src/​Wolverine/​Runtime/​Interop/​CloudEventsMapper.cs Preserves persisted inbox ids during CloudEvent deserialization.
src/​Transports/​Kafka/​Wolverine.Kafka.Tests/​cloudevents_with_default_incoming_message_keeps_inbox_id.cs Checks that successful handling does not leave the inbox row Incoming.
src/​Testing/​CoreTests/​Runtime/​Interop/​CloudEventsMapper_non_guid_id_tests.cs Tests GUID id behavior for persisted and non-persisted envelopes.

Review effort: Balanced

Comment on lines +56 to +73
Id = storedId, WasPersistedInInbox = true, Data = Encoding.UTF8.GetBytes(GuidIdJson(Guid.NewGuid()))
};
_mapper.ReadFromData(typeof(Order), envelope);

envelope.Id.ShouldBe(storedId);
}

[Fact]
public void reading_an_envelope_not_held_in_the_inbox_takes_a_guid_cloud_event_id()
{
var cloudEventId = Guid.NewGuid();
var envelope = new Envelope { Id = Guid.NewGuid(), Data = Encoding.UTF8.GetBytes(GuidIdJson(cloudEventId)) };
_mapper.ReadFromData(typeof(Order), envelope);

envelope.Id.ShouldBe(cloudEventId);
}

private static string GuidIdJson(Guid id) =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed to guidIdJson in a766ffd.

@jeremydmiller
jeremydmiller merged commit 1734ba0 into JasperFx:main Nov 6, 2026
49 checks passed
jeremydmiller added a commit that referenced this pull request Nov 6, 2026
…reads the messageId (#5398)

* MassTransit interop keeps the inbox id of a durable envelope when it reads the messageId

MassTransitEnvelope.TransferData always replaced Envelope.Id with the MassTransit
messageId when the handler pipeline deserialized the body. On SQS the MassTransit
mapper leaves the id to the listener, which derives it from the SQS message, and
the durable inbox stores the envelope under that id. Mark-handled, retry and
dead-letter then matched no row, so the inbox row stayed Incoming and ran again
on recovery. Same shape as the CloudEvents case in #5396.

TransferData now leaves the id alone for an envelope that WasPersistedInInbox.
Tests: CoreTests unit test, and an SQS (LocalStack) + PostgreSQL inbox test that
fails without the fix with the row left Incoming.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Reset WithinCodegenCommand after the codegen-command test so later hosts are not MediatorOnly

The GH-5391 test runs `codegen test` / `codegen write` through RunJasperFxCommands,
which leaves the process-wide DynamicCodeBuilder.WithinCodegenCommand flag set.
applyMetadataOnlyModeIfDetected then starts every later host in that test process
in MediatorOnly mode, so whichever CoreTests ran after it in the same worker failed
("This operation is not allowed with Wolverine is bootstrapped in MediatorOnly
mode") -- red on main at 6.62.1, green at a89e663, depending only on test order.

Reset the flag after the commands and in finally, and run the class in a
non-parallel collection so no host starts while the flag is set (the same
arrangement Wolverine.Grpc.Tests uses for this flag).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <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