# Messaging Platform Bridge Design ## Goal Replace the application-specific broker seam with one canonical anti-corruption bridge: ``` application-core IntegrationEventPublishPort -> adapter/outbound/messaging/platformbridge -> messaging-schema-api EncodedMessagePublisher -> messaging-runtime-core DefaultMessagePublisher -> messaging transport/runtime ``` The bridge must preserve canonical event identity and exact encoded bytes while reusing the platform's destination resolution, authorization, admission, runtime leasing, transport normalization, and observation pipeline. ## Scope This phase introduces and verifies the canonical bridge. It does **not** migrate the legacy outbox storage/relay rows, because `OutboxEvent` does not retain the schema/order/tenant metadata required to reconstruct `ValidatedIntegrationEvent` without invention. ## Application boundary Create `IntegrationEventPublishPort` in `application-core`. Signature: ```java CompletionStage publish(ValidatedIntegrationEvent event); ``` The application package depends only on its own canonical event model and application outcome vocabulary. ## Adapter bridge `PlatformIntegrationEventPublishAdapter` lives under: ``` adapter/outbound/messaging/platformbridge ``` It depends on `EncodedMessagePublisher`, never on a concrete broker client, runtime-core implementation, or transport SPI. Mapping rules: - `logicalDestinationId` -> platform `DestinationName`. - `contractId` -> platform `MessageType`. - `payloadVersion` -> `SchemaVersion`. - event and causation identities must parse as UUIDv7; values are preserved exactly. Incompatible identities fail closed before the platform publisher is called. - `occurredAt` is used for both `producedAt` and `occurredAt` until the application canonical model carries a separate production timestamp. The bridge never invents a new timestamp. - producer is an explicit constructor/configuration value. - correlation, partition key, tenant, aggregate order and exact envelope bytes are preserved. - trace context is explicitly absent (`TraceContext.none()`) until the application model owns canonical trace context. - exact `envelopeBytes` become `EncodedMessage` bytes; no re-encoding occurs. - schema/catalog/binding/envelope evidence that has no first-class platform field is preserved as bounded `x-ca-*` headers. - the schema reference subject is the canonical contract id and version is the canonical payload version. ## Outcome mapping Mapping is based on completion **and transmission evidence**, not enum name similarity: - CONFIRMED -> `OutboxPublishOutcome.CONFIRMED`. - AMBIGUOUS -> `OutboxPublishOutcome.AMBIGUOUS`. - REJECTED + NOT_TRANSMITTED -> `REJECTED_BEFORE_SEND`. - REJECTED + any evidence that bytes may have left the process -> `REJECTED_AFTER_BROKER`. Bridge preparation failures are definite pre-send rejection. ## Platform boundary `EncodedMessagePublisher` is owned by `messaging-schema-api`, because `EncodedMessage` is owned there and the dependency direction remains acyclic. `DefaultMessagePublisher` implements both `MessagePublisher` and `EncodedMessagePublisher`. The encoded path skips only codec lookup/encoding; destination resolution, access policy, admission, runtime lease, transport send, deadline handling, result normalization and observation are shared with the normal publish path. The starter exposes one `DefaultMessagePublisher` singleton, which therefore satisfies both public interfaces. ## Spring ownership `MessagingBridgeRootAutoConfiguration` owns the bridge bean when an `EncodedMessagePublisher` is present **and** `app.messaging.producer-id` is explicitly configured. Producer identity is never inferred from `spring.application.name` or invented. Application bootstrap must not construct Kafka producer clients or implement broker-specific send behavior. The existing `KafkaSender` / `KafkaMessageBroker` path remains temporarily for the legacy `OutboxEvent` and realtime publishers, which do not yet carry enough canonical metadata to enter the new bridge without invention. It is explicitly transitional and is removed only with the legacy outbox/realtime cutover. The new canonical bridge never calls it. ## Verification Required checks: 1. `DefaultMessagePublisherTest`: pre-encoded publish preserves bytes and skips codec while still exercising central pipeline. 2. `PlatformIntegrationEventPublishAdapterTest`: golden mapping, outcome mapping, fail-closed identity behavior. 3. outbound messaging module tests/check. 4. messaging runtime/starter tests. 5. app-bootstrap system test and architecture test after adding the canonical bridge while retaining the documented legacy seam. 6. search proving app-bootstrap has no direct native Kafka sender configuration. 7. dependency/build lock refresh only where dependency ownership changed. 8. `git diff --check`.