# evidence 255 — messaging-capability-doc-vs-code-drift # revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5 # cwd: /shared/codebase/clean-architecture-backend-template/src/messaging # command: bash /tmp/capdrift.sh # ---- raw output ---- ==== code: MessagingCapabilities record component order ==== boolean brokerAcknowledgement, boolean replicationOrPersistenceEvidence, boolean perMessageSettlement, boolean batchSettlement, boolean orderedStream, boolean keyedOrdering, boolean replay, boolean delayedDelivery, boolean brokerTransaction, boolean deduplicatedPublish, boolean nativeDeadLetter, boolean topologyManagement) { ==== code: per-adapter CAPABILITIES arrays ==== -- messaging-kafka (KafkaMessagingTransport) private static final MessagingCapabilities CAPABILITIES = new MessagingCapabilities( true, true, true, true, true, true, true, false, true, false, false, true); -- messaging-rabbit (RabbitMessagingTransport) new MessagingCapabilities( true, true, true, false, false, false, false, true, false, false, true, true); -- messaging-kafka-share-experimental (KafkaShareWorkQueueCapability) public static MessagingCapabilities capabilities() { return new MessagingCapabilities( true, true, true, false, false, false, false, false, false, false, false, false); -- messaging-pulsar-experimental (PulsarMessagingTransport, SHARED and KEY_SHARED) private static final MessagingCapabilities SHARED_CAPABILITIES = new MessagingCapabilities( true, true, true, true, false, false, true, true, false, false, true, true); private static final MessagingCapabilities KEY_SHARED_CAPABILITIES = new MessagingCapabilities( true, true, true, true, false, true, true, true, false, false, true, true); -- messaging-nats-experimental (NatsJetStreamTransport) private static final MessagingCapabilities CAPABILITIES = new MessagingCapabilities( true, true, true, true, true, false, true, false, false, true, false, true); ==== doc: docs/messaging/support-matrix.md capability table ==== ## Capability 매트릭스 `MessagingCapabilities`가 런타임에 선언하는 값이다. `false`인 기능을 요구하는 destination profile은 **startup에서 실패**하며, 조용히 약화되지 않는다. | Capability | Kafka | Kafka Share | RabbitMQ | Pulsar | NATS JS | |---|---|---|---|---|---| | brokerAcknowledgement | O | O | O | O | O | | replicationOrPersistenceEvidence | O | O | O | O | O | | perMessageSettlement | O | O | O | O | O | | batchSettlement | O | X | X | O | O | | orderedStream | O | **X** | X | X | O | | keyedOrdering | O | **X** | X | Key_Shared만 | X | | replay | O | X | X | O | O | | delayedDelivery | X | X | retry queue로 대행 | O | X | | brokerTransaction | O | X | X | 미승격 | X | | deduplicatedPublish | O | X | X | X | O | | nativeDeadLetter | X | X | O | O | **X** | | topologyManagement | O | X | O | O | O | Kafka Share Group이 ordering 전부 `X`인 것은 설계 결정이다. share group은 개별 record를 ==== code javadoc: why Kafka deduplicatedPublish is false ==== */ public final class KafkaMessagingTransport implements MessagingTransport { /** * What this adapter can prove, with deduplicated publish set to false. * *
It was {@code true}, justified by producer idempotence. Producer idempotence deduplicates * sequence retries within one producer session: the producer id is reassigned on * restart, so the same logical message published again after a crash is a new sequence and the * broker stores it twice. The capability an application reads is "the broker suppresses duplicate * publishes of a stable id", which needs a persistent message-id store that this platform does * not have. * *
Declaring it true means {@code PublishDeduplication} is accepted and silently does nothing — * the caller believes the broker is deduplicating and skips the idempotency it would otherwise * build. False makes that request a startup failure instead, which is the whole reason the flag * exists. * *
The order of these flags is the record's: brokerAcknowledgement, replication evidence, * per-message settlement, batch settlement, ordered stream, keyed ordering, replay, delayed * delivery, broker transaction, deduplicated publish, native dead letter, topology management. */ private static final MessagingCapabilities CAPABILITIES = ==== doc: support-matrix.md runtime-membership claim ==== 23:> 또한 registry의 messaging leaf는 모두 `runtime_memberships`가 비어 있다. 이는 **build-only / ==== authority: src/messaging/CLAUDE.md runtime-membership section ==== ## Runtime membership이 비어 있다는 것의 의미 **이 절은 한동안 사실이 아닌 채로 남아 있었다.** "registry의 모든 messaging leaf는 `runtime_memberships`가 비어 있고 따라서 build-only"라고 쓰여 있었는데, 다섯 어댑터 remediation이 `messaging-spring-boot-starter`를 `app-bootstrap` 의존성으로 넣으면서 그 closure 전체가 런타임 classpath에 올라갔다. 정확한 목록은 registry가 소유하므로 여기서 세지 않는다 — 세는 순간 다시 drift한다. 지금 의미는 이렇다: - 대부분의 leaf는 `runtime_memberships`에 `app-bootstrap`을 갖는다. **배포된 아티팩트가 싣고 있고, master switch `app.messaging.enabled`로 켜진다.** 기본값은 `false`다. - 여전히 비어 있는 leaf(experimental transport, 일부 codec, bridge, testkit)만이 build-only / incubating이다. - **런타임에 있다는 것과 자격이 증명됐다는 것은 다르다.** Kafka publish 경로만 실브로커 round trip으로 자격 검증됐고(`MessagingLiveRoundTripQualificationTest`), Rabbit·JDBC inbox/outbox·claim check·admin plane은 shipped, inactive, unqualified다. 이 구분은 `MessagingMembershipQualificationTest`가 문서가 아니라 테스트로 붙들고 있다. - 애플리케이션에 새로 배선하려면 registry의 `runtime_memberships`를 먼저 바꾸고 ==== registry truth ==== shipped (runtime_memberships non-empty): 18 messaging-admin-api messaging-admin-runtime messaging-claim-check messaging-cloudevents messaging-core-api messaging-inbox-jdbc-postgresql messaging-kafka messaging-observability messaging-outbox-jdbc-postgresql messaging-policy messaging-rabbit messaging-reliability-api messaging-runtime-core messaging-schema-api messaging-schema-json messaging-security messaging-spring-boot-starter messaging-transport-spi build-only (runtime_memberships == []): 7 messaging-kafka-share-experimental messaging-nats-experimental messaging-pulsar-experimental messaging-schema-avro messaging-schema-protobuf messaging-spring-cloud-stream-bridge messaging-testkit