Split out of #3763 / #3798 so it stops riding along inside the Pulsar flaky-tag work — it is not a Pulsar defect and it will affect any transport that runs the compliance battery under CloudEvents.
What happens
TransportCompliance.will_move_to_dead_letter_queue_with_exception_match drives the DLQ path with ErrorCausingMessage, which carries a Dictionary<int, Exception> describing which attempt should throw what. CloudEvents serializes with System.Text.Json, and Exception does not round-trip through it — the Errors dictionary arrives corrupted, so the handler throws the wrong exception type and the exception-match rule never fires.
with_cloud_events has opted out of that test since before this work, by overriding it with an empty body:
public override Task will_move_to_dead_letter_queue_with_exception_match()
{
return Task.CompletedTask;
}
Why it is worth an issue rather than leaving as-is
Two reasons.
It reported as a PASS. An override that just returns Task.CompletedTask is a green test that runs nothing, so the suite counted coverage it did not have. #3798 changes it to a real [Fact(Skip = ...)] carrying the same explanation, which is honest — but now it is a visible skip that someone will eventually ask about, and this issue is the answer.
The gap is in the shared harness, not in one transport. Any transport wired .InteropWithCloudEvents() and run through TransportCompliance inherits the same hole: dead-lettering by exception type is untested under CloudEvents everywhere, silently. Right now Pulsar is the only CloudEvents fixture in the compliance battery, so it is the only place it shows.
Possible directions
- Make the compliance harness's error-injection payload serializer-agnostic — carry an exception type name plus a message rather than live
Exception instances, and rehydrate handler-side. That fixes it for every transport and every serializer at once, and is probably the right fix.
- Or give
TransportComplianceFixture a way to declare "this fixture's serializer cannot carry arbitrary exception graphs", so the affected test is skipped by declaration rather than by a hand-written override in each transport's test project.
Either way the goal is that a transport gets DLQ-by-exception-type coverage under CloudEvents, instead of an override that has to be re-explained every time someone reads it.
🤖 Generated with Claude Code
https://claude.ai/code/session_0116vfBcKwcjWn8msM4ZjkuA
Split out of #3763 / #3798 so it stops riding along inside the Pulsar flaky-tag work — it is not a Pulsar defect and it will affect any transport that runs the compliance battery under CloudEvents.
What happens
TransportCompliance.will_move_to_dead_letter_queue_with_exception_matchdrives the DLQ path withErrorCausingMessage, which carries aDictionary<int, Exception>describing which attempt should throw what. CloudEvents serializes withSystem.Text.Json, andExceptiondoes not round-trip through it — theErrorsdictionary arrives corrupted, so the handler throws the wrong exception type and the exception-match rule never fires.with_cloud_eventshas opted out of that test since before this work, by overriding it with an empty body:Why it is worth an issue rather than leaving as-is
Two reasons.
It reported as a PASS. An override that just returns
Task.CompletedTaskis a green test that runs nothing, so the suite counted coverage it did not have. #3798 changes it to a real[Fact(Skip = ...)]carrying the same explanation, which is honest — but now it is a visible skip that someone will eventually ask about, and this issue is the answer.The gap is in the shared harness, not in one transport. Any transport wired
.InteropWithCloudEvents()and run throughTransportComplianceinherits the same hole: dead-lettering by exception type is untested under CloudEvents everywhere, silently. Right now Pulsar is the only CloudEvents fixture in the compliance battery, so it is the only place it shows.Possible directions
Exceptioninstances, and rehydrate handler-side. That fixes it for every transport and every serializer at once, and is probably the right fix.TransportComplianceFixturea way to declare "this fixture's serializer cannot carry arbitrary exception graphs", so the affected test is skipped by declaration rather than by a hand-written override in each transport's test project.Either way the goal is that a transport gets DLQ-by-exception-type coverage under CloudEvents, instead of an override that has to be re-explained every time someone reads it.
🤖 Generated with Claude Code
https://claude.ai/code/session_0116vfBcKwcjWn8msM4ZjkuA