2020acknowledged only after a successful re-publish, and an undecodable body is restored, not
2121dropped.
2222
23- Replay safety today is sandbox routing (``to_queue``) + ``dry_run``. The **Replay-Bypass**
24- guard — a ``bq-replay-bypass`` transport header surfaced to handlers so a replay can skip
25- external side-effects (don't re-charge, don't re-email) — is a documented phase two: like the
26- OpenTelemetry ``traceparent`` follow-up, it carries out-of-band metadata as a transport header
27- and so touches the runtime + every transport binding. Until then, sandbox routing is the
28- safe-replay answer.
23+ Replay safety is sandbox routing (``to_queue``) + ``dry_run``, plus the **Replay-Bypass** guard
24+ (``bypass=True``): it stamps a ``bq-replay-bypass`` transport header that the runtime surfaces to
25+ handlers via :func:`babelqueue.is_replay` / :func:`babelqueue.bypass_external_effects`, so a
26+ replay can skip external side-effects that already fired (don't re-charge, don't re-email) — see
27+ :mod:`babelqueue.replay` (ADR-0027). The header rides out of band, so the envelope stays frozen;
28+ it propagates over a real broker only once that broker's transport implements the optional
29+ :class:`~babelqueue.transport.HeaderPublisher` capability (the in-memory transport does today).
2930"""
3031
3132from __future__ import annotations
3435from typing import Any , Callable , Dict , List , Mapping , Optional , Tuple
3536
3637from .codec import EnvelopeCodec
37- from .transport import ReceivedMessage , Transport
38+ from .replay import HEADER_REPLAY_BYPASS
39+ from .transport import HeaderPublisher , ReceivedMessage , Transport
3840
3941Envelope = Mapping [str , Any ]
4042Select = Callable [[Envelope ], bool ]
@@ -51,6 +53,7 @@ class RedriveItem:
5153 from_queue : str
5254 to : str # target queue (the plan, even on a dry run; "" when skipped/undecodable)
5355 redriven : bool # True only when actually re-published to ``to``
56+ bypassed : bool = False # True when the bq-replay-bypass header was stamped on the message
5457
5558
5659@dataclass
@@ -70,6 +73,7 @@ def redrive(
7073 max : int = 0 ,
7174 dry_run : bool = False ,
7275 select : Optional [Select ] = None ,
76+ bypass : bool = False ,
7377 timeout : float = 1.0 ,
7478) -> RedriveResult :
7579 """Move dead-lettered messages off ``dlq`` and replay them; see the module docstring."""
@@ -125,7 +129,9 @@ def redrive(
125129 reset .pop ("dead_letter" , None )
126130 reset ["attempts" ] = 0
127131 try :
128- transport .publish (target , EnvelopeCodec .encode (reset ))
132+ item .bypassed = _publish_redriven (
133+ transport , target , EnvelopeCodec .encode (reset ), bypass
134+ )
129135 except Exception :
130136 transport .publish (dlq , message .body ) # restore on a publish failure, then surface
131137 transport .ack (message )
@@ -150,6 +156,17 @@ def _decoded(body: str) -> Optional[Dict[str, Any]]:
150156 return envelope
151157
152158
159+ def _publish_redriven (transport : Transport , queue : str , body : str , bypass : bool ) -> bool :
160+ """Re-publish a reset message to ``queue``. When ``bypass`` is set and the transport is a
161+ :class:`~babelqueue.transport.HeaderPublisher`, stamp the ``bq-replay-bypass`` header and
162+ return True; otherwise publish plainly and return False."""
163+ if bypass and isinstance (transport , HeaderPublisher ):
164+ transport .publish_with_headers (queue , body , {HEADER_REPLAY_BYPASS : "1" })
165+ return True
166+ transport .publish (queue , body )
167+ return False
168+
169+
153170def _source_queue_of (envelope : Envelope ) -> str :
154171 """Default redrive target: ``dead_letter.original_queue``, falling back to ``meta.queue``."""
155172 dead_letter = envelope .get ("dead_letter" )
0 commit comments