# How must an instance preserve reply-routing identity across endpoint redeploys? **Date of Record:** 2026 November 2 **Scope:** Applies to request-reply protocols where the responder routes replies to a return address that may change (endpoint URL, socket address, connection identity) between request and reply. Does not apply to protocols where the reply travels on the same connection the request arrived on. ## Core Distinction A return address is a routing decision made at request time and applied at reply time. Between the two, the requester's endpoint can redeploy, change IP, change port, or move to a different cluster. The reply is well-formed and correctly addressed to where the requester *was*, not where the requester *is*. **Invariant.** Reply routing is sound if and only if `routing_identity(request_time) = routing_identity(reply_time)`, where routing identity is an identifier stable across redeploy (stable service name, logical address resolved at reply time), not a resolved address. **Violation.** The **Ghost Reply**. Requester dispatches from IP A, reply routes to IP A, but IP A has been reassigned to a different service after requester redeploy. Reply is delivered to a party that never sent the request, which either drops it (orphan) or, worse, forwards it to a system that treats it as unrelated input. **Asymmetry.** The return address was valid at request time. The reply is correctly routed to that address. The failure is that the address no longer means what it did, and no layer observes the reassignment. **Recorded elements.** The `routing-identity-receipt` (what identity is used to route the reply), the `redeploy-signature` (whether the requester redeployed between request and reply), and the `address-reassignment-receipt` (whether the resolved address changed owners) together make the routing checkable. ## What Was Observed **Signature.** Replies delivered to a service that did not send the request. The receiving service logs "unknown request id" or, if it does not correlate, processes the reply as unrelated input. The requester times out. **Trace.** Request sent from pod IP 10.0.1.5 at t=0. Pod redeploys at t=30s, new pod IP 10.0.1.9. Old IP is reassigned to a different service at t=45s. Reply sent at t=60s to 10.0.1.5. The different service receives the reply and either drops it or processes it as its own. Requester at 10.0.1.9 times out. **Why monitoring stays green.** Reply sent successfully. Receiver healthy. Requester sees a timeout on a specific request, which is normal under load. The routing failure is invisible because it is a successful delivery to the wrong party. ## Condition of Validity The instance must ensure at least one holds before it relies on a reply-routing address: 1. **Must route by stable identity.** The return address is a stable identifier (service DNS name, logical ID) resolved at reply time, not a resolved address recorded at request time. 2. **Must bind reply to request identity.** The reply carries a request id and a sender identity; the receiver verifies both before processing, so a misdirected reply is rejected rather than processed. 3. **Must keep connection.** The reply travels on the same connection as the request, eliminating the routing decision entirely. ## Decision Boundary - Stable identity, resolved at reply time: sound. - Resolved address, connection persistent: sound as long as connection is not lost. - Resolved address, connection ephemeral: unsound; requester redeploy between request and reply misdirects. - Resolved address with no request-id correlation: misdirected replies are processed by the wrong party; unsound by construction. ## Operational Effect The invariant couples a return address captured at request time to an endpoint topology that changes. The instance's correct posture is to route by stable identity and to correlate replies. **Trap.** Increase the timeout to allow for redeploy. Longer timeouts increase the window in which redeploy can occur, so the failure rate rises with the timeout. The fix is stable identity, not a longer window. ```python from dataclasses import dataclass class GhostReplyError(RuntimeError): """Raised when a reply address recorded at request time may not identify the requester at reply time.""" @dataclass(frozen=True) class ReplyRoutingGate: routing_by_stable_identity: bool connection_persistent: bool reply_carries_request_identity: bool def verify(self) -> bool: if self.routing_by_stable_identity: return True if self.connection_persistent: return True if not self.reply_carries_request_identity: raise GhostReplyError( "resolved-address routing with no request identity: " "a redeployed requester misdirects replies to a new owner" ) raise GhostReplyError( "resolved-address routing is unsound across redeploy; use stable identity" ) def dispatch_reply_routing_mode(gate: ReplyRoutingGate) -> str: try: gate.verify() except GhostReplyError: return "route-by-stable-identity" return "resolved-address-routing" ```