gobridge

0008 — Cross-hop bridge-to-bridge identity lift

Status: accepted Date: 2026-07-04 Deciders: GoBridge core

Context

Three bridge-to-bridge propagated reserved headers carry the identity a downstream bridge needs to deduplicate, preserve ordering, and correlate a message across a bridge → broker → bridge relay: x-bridge.idempotency-key, x-bridge.dedup-id, x-bridge.ordering-key. Lose them at a hop and dedup and ordering suppression break downstream of the relay.

ADR 0001 strips every x-bridge.* key from untrusted transport input at ingress (StripReservedHeaders, domain/messaging/headers.go). A message arriving on a transport is external input, so the strip is unconditional and correct — but it erases the three identity keys along with everything else, so a naive relay drops the identity at each receiving hop.

NewEnvelope funnels every constructed envelope through that strip: NewHeadersFromMap applies StripReservedHeaders semantics to the caller-supplied header map (domain/messaging/envelope.go, domain/messaging/headers.go). Any value that must survive ingress has to enter by another door.

Decision

Lift the three identity keys from the ingress wire into the typed EnvelopeInput fields IdempotencyKey / DeduplicationID / OrderingKey (domain/messaging/envelope.go). NewEnvelope re-stamps them into their reserved headers with SetHeader after the strip (domain/messaging/envelope.go) — the same typed-field, post-strip mechanism ADR 0001 uses for the route override.

The difference from ADR 0001: there the value originates in-process (an internally-constructed binding override). Here the value originates from untrusted wire data — an SQS message attribute, an AMQP application property, or an HTTP header — any of which a principal with broker/queue write can set. The lift reads that wire value into a typed field by design, then lets NewEnvelope re-stamp it after the strip has run.

Per-transport sources (the lift is the sole path; the wire header is still stripped):

The lift is unconditional — none of the three converters consult trust_bridge_headers. That flag is a route-policy field (domain/routing/policy.go) evaluated later in the route runner (runtime/route/runner.go); it governs preservation of the broader bridge-to-bridge header set (correlation-id, causation-id, tenant-id, forwarded-*, …). Identity must survive even when that broader preservation is off, so the three keys are lifted regardless.

Consequences

Rejected alternatives