Skip to content

RMQ: no invalid-message destination is modelled, so unacceptable rejections dead-letter to the DLQ #4387

Description

@iancooper

Summary

Neither RMQ gateway models an invalid-message destination. A rejection carrying
RejectionReason.UnacceptableMessage dead-letters to the DLQ along with delivery errors, so the two
rejection classes are indistinguishable at the destination.

This holds conformance behaviour FR-5 (reject with unacceptable reason routes to the invalid
channel
) Deferred on three cells: RMQ.Async / Classic, RMQ.Async / Quorum and
RMQ.Sync / RmqSyncMessagingGateway. Split out of the #4240 umbrella so it is tracked on its own.

Evidence

From the conformance run against a live RabbitMQ 4.2 broker, both variants: the provider's real
invalid-channel read hook (not a Message.Empty stub) observes MT_NONE after an unacceptable
rejection, while the DLQ read hook observes the message.

specs/0036-universal-transport-conformance-tests/conformance-status.md records the measurement and the
three deferral preconditions being met.

Cause

grep -rn --include='*.cs' 'InvalidMessageRoutingKey' src/Paramore.Brighter.MessagingGateway.RMQ.Async/ src/Paramore.Brighter.MessagingGateway.RMQ.Sync/ returns nothing. Neither RmqSubscription nor
RmqMessageConsumer carries an invalid destination, and neither package implements
IUseBrighterInvalidMessageSupport.

RMQ's rejection path is a native BasicReject, which dead-letters through the single configured DLX
(x-dead-letter-exchange / x-dead-letter-routing-key). One exchange, one destination — the reason
cannot influence where the message lands.

This is a routing gap, not a metadata gap, so it is not covered by the maintainer-approved FR-8
relaxation (which lets a natively dead-lettering transport conform on routing alone). FR-4, FR-6 and
FR-17 all Pass for RMQ because the DLQ is the right destination for those; FR-5 is the one case where
a second destination is required and none exists.

Suggested direction

Brighter-managed invalid routing in src/Paramore.Brighter.MessagingGateway.RMQ.Async (and RMQ.Sync):
RmqSubscription implements IUseBrighterInvalidMessageSupport, the consumer factory wires an
invalid-message producer, and RejectAsync republishes to the invalid routing key when
reason == UnacceptableMessage before BasicReject-ing the original — the shape the nine gateways that
already implement the interface use (RocketMQ, AWSSQS ×2, Redis, Postgres, Kafka, MsSql, MQTT).

Note that RMQ is also the transport where RejectionMetadataKeys are empty, so a Brighter-managed
invalid path would additionally let RMQ stamp real rejection metadata rather than relying on the
relaxation.

Contrast Azure Service Bus, whose FR-5 deferral is a genuine platform difference and is not in
scope here: ASB's DLQ is a built-in sub-queue and there is no second entity to route to.

Scope

  • 3 ledger cells: RMQ.Async / Classic FR-5, RMQ.Async / Quorum FR-5, RMQ.Sync FR-5
  • Repoint those cells from Deferred -> #4240 to this issue

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions