@@ -279,7 +279,11 @@ def dispatch(self, received: ReceivedMessage) -> None:
279279 with _headers_scope (received .headers ), _replay_scope (
280280 bool (received .headers .get (HEADER_REPLAY_BYPASS ))
281281 ):
282- envelope = EnvelopeCodec .decode (received .body )
282+ # A version-gated delivery is rejected without decoding its body (broker-bindings
283+ # §3.7/§4.7/§5.7/§6.7/§7.7): the raw body goes to the DLQ byte-identical, so there is
284+ # no envelope to read here.
285+ gated = received .route_urn .startswith (_UNSUPPORTED_SCHEMA_ROUTE_PREFIX )
286+ envelope = {} if gated else EnvelopeCodec .decode (received .body )
283287 urn = str (envelope .get ("job" ) or envelope .get ("urn" ) or "" )
284288 if received .route_urn and envelope :
285289 # Transport-supplied routing override; body untouched. Only for a body that
@@ -479,7 +483,7 @@ def _route_unknown(self, urn: str, received: ReceivedMessage, envelope: Mapping[
479483 # gate would be bypassed or the message would loop. It goes straight to the DLQ (or is
480484 # dropped when no DLQ is configured).
481485 if received .route_urn .startswith (_UNSUPPORTED_SCHEMA_ROUTE_PREFIX ):
482- self ._settle_version_gated (received , envelope )
486+ self ._settle_version_gated (received )
483487 return
484488 # An undecodable body or a body naming no URN can never gain a handler either, so
485489 # releasing it unchanged would redeliver it forever. It takes the bounded FAIL path
@@ -495,11 +499,21 @@ def _route_unknown(self, urn: str, received: ReceivedMessage, envelope: Mapping[
495499 # (never kills the loop).
496500 raise UnknownUrnError (f"No handler mapped for URN [{ urn or '(empty)' } ]." )
497501
498- def _settle_version_gated (self , received : ReceivedMessage , envelope : Mapping [str , Any ]) -> None :
499- """Dead-letter a version-gated delivery directly (no retry); drop it when no DLQ is set."""
502+ def _dead_letter_target (self , received : ReceivedMessage ) -> str :
503+ """The queue a dead-lettered delivery is written to (configured DLQ or ``<queue><suffix>``)."""
504+ return self .dead_letter_queue or (received .queue + self .dead_letter_suffix )
505+
506+ def _settle_version_gated (self , received : ReceivedMessage ) -> None :
507+ """Dead-letter a version-gated delivery directly (no retry); drop it when no DLQ is set.
508+
509+ The gate rejects the message *without decoding it*, so the DLQ record is the incoming raw
510+ body, byte-identical, with no ``dead_letter`` block (matching the Java, .NET and Node
511+ SDKs). A failed DLQ write propagates: the delivery is left unacknowledged and handed back
512+ (see :meth:`dispatch`), never retried.
513+ """
500514 if self .dead_letter_enabled or self .on_unknown_urn == UnknownUrnStrategy .DEAD_LETTER :
501- error = UnknownUrnError ( f"No handler mapped for URN [ { received .route_urn } ]." )
502- self ._dead_letter (received , dict ( envelope ), "unknown_urn" , error )
515+ self . _publish_for ( received , self . _dead_letter_target ( received ), received .body )
516+ self ._ack_settled (received )
503517 return
504518 self ._ack_settled (received )
505519
@@ -537,8 +551,9 @@ def _dead_letter(
537551 error = (str (exc ) if exc is not None else None ),
538552 exception = (type (exc ).__name__ if exc is not None else None ),
539553 )
540- target = self .dead_letter_queue or (received .queue + self .dead_letter_suffix )
541- self ._publish_for (received , target , EnvelopeCodec .encode (annotated ))
554+ self ._publish_for (
555+ received , self ._dead_letter_target (received ), EnvelopeCodec .encode (annotated )
556+ )
542557 self ._ack_settled (received )
543558
544559
0 commit comments