# 선택기가 rabbit 을 거절하는 자리와 그 필드 자바독
  MessagingProviderSelection.java:48           "kafka", KafkaMessagingAutoConfiguration.class,
  MessagingProviderSelection.java:49           "rabbit", RabbitMessagingAutoConfiguration.class);
  MessagingProviderSelection.java:50 
  MessagingProviderSelection.java:51   /**
  MessagingProviderSelection.java:52    * Broker ids that ship without a {@code MessagingTransport}, and why.
  MessagingProviderSelection.java:53    *
  MessagingProviderSelection.java:54    * <p>Registered is not the same as assemblable. The Rabbit leaf ships its profile validator,
  MessagingProviderSelection.java:55    * failure classifier and security configurer, and no transport — the native channel bridge those
  MessagingProviderSelection.java:56    * would sit on has only test implementations. Selecting it therefore produced an unsatisfied
  MessagingProviderSelection.java:57    * dependency on {@code MessagingTransport} deep inside the core configuration, which tells an
  MessagingProviderSelection.java:58    * operator that a bean is missing rather than that the transport they chose is not finished.
  MessagingProviderSelection.java:59    *
  MessagingProviderSelection.java:60    * <p>Refused here instead, next to the other selection errors, so the message names the state the
  MessagingProviderSelection.java:61    * deployment is actually in. An entry leaves this map on the day its transport does exist.
  MessagingProviderSelection.java:62    */
  MessagingProviderSelection.java:63   static final Map<String, String> BROKERS_WITHOUT_A_TRANSPORT =
  MessagingProviderSelection.java:64       Map.of(
  MessagingProviderSelection.java:65           "rabbit",
  MessagingProviderSelection.java:66           "the Rabbit adapter ships its validators and security configuration but no "
  MessagingProviderSelection.java:67               + "MessagingTransport: its native channel publisher is not implemented, so a publish "
  MessagingProviderSelection.java:68               + "has nothing to travel on");
  MessagingProviderSelection.java:69 
  MessagingProviderSelection.java:70   /** The registered broker ids, so a message can tell an operator what is available. */
  MessagingProviderSelection.java:118               + clientClass
  MessagingProviderSelection.java:119               + " on the classpath; the deployment selected a transport it did not ship");
  MessagingProviderSelection.java:120     }
  MessagingProviderSelection.java:121     String unfinished = BROKERS_WITHOUT_A_TRANSPORT.get(broker);
  MessagingProviderSelection.java:122     if (unfinished != null) {
  MessagingProviderSelection.java:123       throw new IllegalStateException(
  MessagingProviderSelection.java:124           MessagingSettings.PREFIX
  MessagingProviderSelection.java:125               + ".broker="
  MessagingProviderSelection.java:126               + broker
  MessagingProviderSelection.java:127               + " cannot be selected: "
  MessagingProviderSelection.java:128               + unfinished
  MessagingProviderSelection.java:129               + ". Transports that can be assembled today: "
  MessagingProviderSelection.java:130               + assemblableBrokerIds());
  MessagingProviderSelection.java:131     }
  MessagingProviderSelection.java:132     return broker;

# 그 거절을 실제로 불러 본 결과
    등록된 이름     : [kafka, rabbit]
    조립 가능한 이름 : [kafka]
    broker=kafka    → 선택됨 : kafka
    broker=rabbit   → IllegalStateException
      app.messaging.broker=rabbit cannot be selected: the Rabbit adapter ships its validators 
      and security configuration but no MessagingTransport: its native channel publisher is no
      t implemented, so a publish has nothing to travel on. Transports that can be assembled t
      oday: [kafka]
    broker=pulsar   → IllegalStateException
      app.messaging.broker=pulsar is not a registered transport; registered: [kafka, rabbit]
    broker=(빈 값)    → IllegalStateException
      app.messaging.enabled=true needs a transport; set app.messaging.broker to one of [kafka,
       rabbit]

# 그 거절을 붙드는 시험이 없다
  MessagingProviderSelection 을 참조하는 파일 : 4 개
    src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingCoreAutoConfiguration.java
    src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingPlatformRootAutoConfiguration.java
    src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingProviderSelection.java
    src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java
  env 레지스트리가 요구하는 시험 id :
    env-keys.yaml:3326  required_test: adapter-contract:messaging-broker-selection
  그 id 를 정의한 자리 : 0 개

# RabbitChannelPublisher 가 요구하는 추상 메서드 (람다로 구현될 수 없다)
  RabbitChannelPublisher.java:18    * @return the sequence number the next publish will carry
  RabbitChannelPublisher.java:19    */
  RabbitChannelPublisher.java:20   long nextPublishSequence();
  RabbitChannelPublisher.java:21 
  RabbitChannelPublisher.java:22   /**
  RabbitChannelPublisher.java:23    * Publishes a message under a previously reserved sequence number.
  RabbitChannelPublisher.java:24    *
  RabbitChannelPublisher.java:25    * @param sequence the reserved sequence number
  RabbitChannelPublisher.java:26    * @param exchange the exchange to publish to
  RabbitChannelPublisher.java:27    * @param routingKey the routing key
  RabbitChannelPublisher.java:28    * @param message the AMQP message
  RabbitChannelPublisher.java:29    * @param mandatory whether the broker must return an unroutable message
  RabbitChannelPublisher.java:30    */
  RabbitChannelPublisher.java:31   void publish(
  RabbitChannelPublisher.java:32       long sequence, String exchange, String routingKey, Message message, boolean mandatory);
  RabbitChannelPublisher.java:33 
  RabbitChannelPublisher.java:34   /**
  RabbitChannelPublisher.java:35    * Closes the underlying channel.
  RabbitChannelPublisher.java:36    *
  RabbitChannelPublisher.java:37    * <p>A default no-op so an existing publisher keeps compiling, but the transport calls it: a
  RabbitChannelPublisher.java:38    * transport whose {@code close} only flipped a boolean left the native channel open and every
  RabbitChannelPublisher.java:39    * pending stage waiting for a confirm that would never arrive.
  RabbitChannelPublisher.java:40    */
  RabbitChannelPublisher.java:41   default void close() {}
  그 이름이 나오는 자리 전부 :
  messaging/messaging-rabbit · main · RabbitChannelPublisher.java:13  public interface RabbitChannelPublisher {
  messaging/messaging-rabbit · main · RabbitMessagingTransport.java:47  private final RabbitChannelPublisher channel;
  messaging/messaging-rabbit · main · RabbitMessagingTransport.java:61  String brokerName, long generation, RabbitChannelPublisher channel) {
  messaging/messaging-rabbit · main · RabbitMessagingTransport.java:84  RabbitChannelPublisher channel,
  messaging/messaging-rabbit · test · RabbitBrokerIT.java:93  new RabbitChannelPublisher() {
  messaging/messaging-rabbit · test · RabbitRuntimeTest.java:48  new RabbitChannelPublisher() {
  implements 를 가진 파일 : 0 개 · 익명 구현을 가진 파일 : 2 개
  [대조] implements MessagingTransport 를 가진 파일 :
  messaging/messaging-kafka · main · KafkaMessagingTransport.java:41  public final class KafkaMessagingTransport implements MessagingTransport {
  messaging/messaging-nats-experimental · main · NatsJetStreamTransport.java:54  public final class NatsJetStreamTransport implements MessagingTransport {
  messaging/messaging-pulsar-experimental · main · PulsarMessagingTransport.java:47  public final class PulsarMessagingTransport implements MessagingTransport {
  messaging/messaging-rabbit · main · RabbitMessagingTransport.java:39  public final class RabbitMessagingTransport implements MessagingTransport {
  messaging/messaging-runtime-core · test · DefaultMessagePublisherTest.java:426  private static final class RecordingTransport implements MessagingTransport {
  messaging/messaging-transport-spi · test · MessagingRuntimeRegistryTest.java:230  final class FakeTransport implements MessagingTransport {

# 그 두 익명 구현이 각각 무엇을 하는가
  RabbitBrokerIT.java:89     transport =
  RabbitBrokerIT.java:90         new RabbitMessagingTransport(
  RabbitBrokerIT.java:91             "rabbit-primary",
  RabbitBrokerIT.java:92             1,
  RabbitBrokerIT.java:93             new RabbitChannelPublisher() {
  RabbitBrokerIT.java:94               @Override
  RabbitBrokerIT.java:95               public long nextPublishSequence() {
  RabbitBrokerIT.java:96                 return channel.getNextPublishSeqNo();
  RabbitBrokerIT.java:97               }
  RabbitBrokerIT.java:98 
  RabbitBrokerIT.java:99               @Override
  RabbitBrokerIT.java:100               public void publish(
  RabbitBrokerIT.java:101                   long sequence,
  RabbitBrokerIT.java:102                   String exchange,
  RabbitBrokerIT.java:103                   String routingKey,
  RabbitBrokerIT.java:104                   org.springframework.amqp.core.Message message,
  RabbitBrokerIT.java:105                   boolean mandatory) {
  RabbitBrokerIT.java:106                 RabbitBrokerIT.this.publish(sequence, exchange, routingKey, message, mandatory);
  RabbitBrokerIT.java:107               }
  RabbitBrokerIT.java:108             });

  RabbitRuntimeTest.java:44   private RabbitMessagingTransport transport() {
  RabbitRuntimeTest.java:45     return new RabbitMessagingTransport(
  RabbitRuntimeTest.java:46         "rabbit-primary",
  RabbitRuntimeTest.java:47         1,
  RabbitRuntimeTest.java:48         new RabbitChannelPublisher() {
  RabbitRuntimeTest.java:49           @Override
  RabbitRuntimeTest.java:50           public long nextPublishSequence() {
  RabbitRuntimeTest.java:51             return sequence.incrementAndGet();
  RabbitRuntimeTest.java:52           }
  RabbitRuntimeTest.java:53 
  RabbitRuntimeTest.java:54           @Override
  RabbitRuntimeTest.java:55           public void publish(
  RabbitRuntimeTest.java:56               long sequenceNumber,
  RabbitRuntimeTest.java:57               String exchange,
  RabbitRuntimeTest.java:58               String routingKey,
  RabbitRuntimeTest.java:59               Message message,
  RabbitRuntimeTest.java:60               boolean mandatory) {
  RabbitRuntimeTest.java:61             published.add(message);
  RabbitRuntimeTest.java:62           }
  RabbitRuntimeTest.java:63         });
  그 IT 가 붙는 브로커 :
    RabbitBrokerIT.java:40  import org.testcontainers.rabbitmq.RabbitMQContainer;
    RabbitBrokerIT.java:60  @Container
    RabbitBrokerIT.java:61  private static final RabbitMQContainer RABBIT = new RabbitMQContainer("rabbitmq:4.3-management");
    RabbitBrokerIT.java:154  assertThat(channel.messageCount(QUEUE)).isEqualTo(1);
    RabbitBrokerIT.java:161  assertThat(channel.messageCount(QUEUE)).isZero();
    RabbitBrokerIT.java:187  channel.basicPublish(exchange, routingKey, mandatory, properties, message.getBody());

# 채택자가 seam 을 구현하면 고를 수 있는가
  messaging-rabbit/build.gradle:11 
  messaging-rabbit/build.gradle:12     // api: RabbitChannelPublisher and the mappers expose Spring AMQP's Message on public
  messaging-rabbit/build.gradle:13     // signatures, so a consumer implementing the seam needs the type on its compile classpath.
  messaging-rabbit/build.gradle:14     api 'org.springframework.amqp:spring-rabbit'
  KafkaMessagingAutoConfiguration.java:162   @Bean
  KafkaMessagingAutoConfiguration.java:163   @ConditionalOnMissingBean(dev.caskeleton.messaging.transport.MessagingTransport.class)
  KafkaMessagingAutoConfiguration.java:164   public dev.caskeleton.messaging.kafka.KafkaMessagingTransport messagingKafkaTransport(
  KafkaMessagingAutoConfiguration.java:165       org.apache.kafka.clients.producer.Producer<byte[], byte[]> producer) {
  MessagingProviderSelection.java:144 
  MessagingProviderSelection.java:145     @Override
  MessagingProviderSelection.java:146     public String[] selectImports(AnnotationMetadata importingClassMetadata) {
  MessagingProviderSelection.java:147       return new String[] {PROVIDER_CONFIGURATIONS.get(selectedBroker(environment)).getName()};
  MessagingProviderSelection.java:148     }
  MessagingProviderSelection.java:149   }

# 두 자동설정이 선언하는 빈
  KafkaMessagingAutoConfiguration :
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:41:  public KafkaProfileValidator kafkaProfileValidator() {
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:54:  public StartupProfileValidation<dev.caskeleton.messaging.kafka.KafkaBrokerProfile>
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:74:  public KafkaTransactionProfileValidator kafkaTransactionProfileValidator() {
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:85:  public KafkaPublishFailureClassifier kafkaPublishFailureClassifier() {
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:109:  public KafkaSecurityConfigurer kafkaSecurityConfigurer(
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:130:  public org.apache.kafka.clients.producer.Producer<byte[], byte[]> messagingKafkaProducer(
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:164:  public dev.caskeleton.messaging.kafka.KafkaMessagingTransport messagingKafkaTransport(
  RabbitMessagingAutoConfiguration :
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java:35:  public RabbitProfileValidator rabbitProfileValidator() {
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java:48:  public StartupProfileValidation<dev.caskeleton.messaging.rabbit.RabbitBrokerProfile>
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java:70:  public RabbitPublishFailureClassifier rabbitPublishFailureClassifier() {
src/messaging/messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java:94:  public RabbitSecurityConfigurer rabbitSecurityConfigurer(

# 그 어댑터가 출하 아티팩트에 들어가는가
  modules.json:417       "id": "messaging-rabbit",
  modules.json:418       "gradle_path": ":messaging:messaging-rabbit",
  modules.json:419       "source_path": "src/messaging/messaging-rabbit",
  modules.json:420       "allowed_dependencies": [
  modules.json:421         "messaging-core-api",
  modules.json:422         "messaging-schema-api",
  modules.json:423         "messaging-policy",
  modules.json:424         "messaging-transport-spi",
  modules.json:425         "messaging-observability",
  modules.json:426         "messaging-security",
  modules.json:427         "messaging-admin-api"
  modules.json:428       ],
  modules.json:429       "runtime_memberships": [
  modules.json:430         "app-bootstrap"
  modules.json:431       ]
  app-bootstrap/gradle.lockfile:77  com.rabbitmq:amqp-client:5.27.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
  main 자바 파일 : 20 개 · 물리적 줄 2443 · 빈 줄 제외 2232
  그 전송 클래스가 자기를 뭐라고 부르는가 :
    RabbitMessagingTransport.java:27  * The Stable RabbitMQ adapter.
    CompatibilityMatrix.java:86  "messaging-rabbit",
    CompatibilityMatrix.java:92  new Entry("messaging-rabbit", List.of("4.3"), Tier.EXPERIMENTAL, true, false),

# 운영 문서가 이 브로커를 어떻게 적는가
  support-matrix.md:29 ## 브로커 등급
  support-matrix.md:30 
  support-matrix.md:31 | 브로커 | 등급 | 인증 기준 | Stable 기능 | 제한 |
  support-matrix.md:32 |---|---|---|---|---|
  support-matrix.md:33 | Kafka | Stable | 4.1.x | producer idempotence, consumer group, batch, pause/resume, replay, transaction capability | Share Group은 Experimental |
  support-matrix.md:34 | RabbitMQ | Experimental | 4.3.x | exchange/routing, publisher confirm, mandatory return, manual ACK, quorum queue, retry queue, DLQ | 장애 시나리오 레인 미실행 — 증거 없음. stream 및 특수 plugin 미지원 |
  support-matrix.md:35 | Pulsar | Experimental | 4.0 LTS + 4.2 | typed publish/consume, Shared, Key_Shared, schema | transaction 미승격, 기본 비활성 |
  support-matrix.md:36 | NATS JetStream | Experimental | 2.14.x | stream, durable consumer, explicit ACK, dedupe, replay | native DLQ 없음(플랫폼이 대행), 기본 비활성 |
  support-matrix.md:37 | Artemis/JMS | Extension | 범위 밖 | adapter SPI만 | 별도 ADR + Contract Suite 통과 필요 |
  support-matrix.md:38 
  그 표 위에 붙은 단서 :
  support-matrix.md:22 >
  support-matrix.md:23 > 또한 registry의 messaging leaf는 모두 `runtime_memberships`가 비어 있다. 이는 **build-only /
  support-matrix.md:24 > incubating** — 어느 composition root에도 편입되지 않았다는 뜻이며, 아래의 등급과는 다른 축이다.
  support-matrix.md:25 > 등급은 "무엇이 증명되었는가", membership은 "무엇이 실행되는가"를 말한다. 애플리케이션에 배선하려면
  support-matrix.md:26 > registry를 먼저 바꾸고 `verifyRuntimeModuleMembership`을 통과시켜야 한다. 자세한 규칙은
  support-matrix.md:27 > `src/messaging/CLAUDE.md`가 소유한다.
  설정 참조 문서의 RabbitMQ 절 :
  configuration-reference.md:132 ### RabbitMQ
  configuration-reference.md:133 
  configuration-reference.md:134 ```yaml
  configuration-reference.md:135 app:
  configuration-reference.md:136   messaging:
  configuration-reference.md:137     brokers:
  configuration-reference.md:138       rabbit-primary:
  configuration-reference.md:139         type: rabbitmq
  configuration-reference.md:140         stable: true
  configuration-reference.md:141         production: false
  configuration-reference.md:142         addresses: [rabbit-1:5671]
  configuration-reference.md:143         publisher-confirms: true    # stable에서 필수
  configuration-reference.md:144         publisher-returns: true     # stable에서 필수
  configuration-reference.md:145         mandatory: true             # stable에서 필수
  configuration-reference.md:146         confirm-timeout: 5s
  env-keys.yaml:3313   - name: APP_MESSAGING_BROKER
  env-keys.yaml:3318     default: null
  env-keys.yaml:3319     allowed_values: null
  env-keys.yaml:3324     validation: none
  env-keys.yaml:3326     required_test: adapter-contract:messaging-broker-selection

# 그 표를 지키는 문서 계약 시험이 무엇을 단언하는가
  그 파일의 시험 개수 : 8 개
    MessagingDocumentationContractTest.java:43  void everyDocumentTheSupportMatrixPromisesExists() {
    MessagingDocumentationContractTest.java:61  void theSupportMatrixNamesExactlyTheAdaptersTheCodeCallsStable() {
    MessagingDocumentationContractTest.java:70  void theSupportMatrixDoesNotCallAnExperimentalAdapterStable() {
    MessagingDocumentationContractTest.java:83  void theDocumentedKafkaVersionsMatchWhatTheCodeCertifies() {
    MessagingDocumentationContractTest.java:92  void theUnsupportedListStillNamesTheTwoConstantsThatDoNotExist() {
    MessagingDocumentationContractTest.java:102  void noEnumConstantTheDocsDenyActuallyExists() {
    MessagingDocumentationContractTest.java:115  void theExperimentalPolicyStatesThatExperimentalIsOffByDefault() {
    MessagingDocumentationContractTest.java:120  void everyDocumentHasContent() {
  MessagingDocumentationContractTest.java:59 
  MessagingDocumentationContractTest.java:60   @Test
  MessagingDocumentationContractTest.java:61   void theSupportMatrixNamesExactlyTheAdaptersTheCodeCallsStable() {
  MessagingDocumentationContractTest.java:62     String matrix = read("support-matrix.md");
  MessagingDocumentationContractTest.java:63 
  MessagingDocumentationContractTest.java:64     CompatibilityMatrix.entries().values().stream()
  MessagingDocumentationContractTest.java:65         .filter(entry -> entry.tier() == CompatibilityMatrix.Tier.STABLE)
  MessagingDocumentationContractTest.java:66         .forEach(entry -> assertThat(matrix).contains(brokerLabel(entry.adapter())));
  MessagingDocumentationContractTest.java:67   }
  MessagingDocumentationContractTest.java:68 
  MessagingDocumentationContractTest.java:69   @Test
  MessagingDocumentationContractTest.java:70   void theSupportMatrixDoesNotCallAnExperimentalAdapterStable() {
  MessagingDocumentationContractTest.java:71     String matrix = read("support-matrix.md");
  MessagingDocumentationContractTest.java:72 
  MessagingDocumentationContractTest.java:73     CompatibilityMatrix.entries().values().stream()
  MessagingDocumentationContractTest.java:74         .filter(entry -> entry.tier() == CompatibilityMatrix.Tier.EXPERIMENTAL)
  MessagingDocumentationContractTest.java:75         .forEach(
  MessagingDocumentationContractTest.java:76             entry ->
  MessagingDocumentationContractTest.java:77                 assertThat(matrix)
  MessagingDocumentationContractTest.java:78                     .as("%s is Experimental in code", entry.adapter())
  MessagingDocumentationContractTest.java:79                     .doesNotContain("| %s | Stable".formatted(brokerLabel(entry.adapter()))));
  MessagingDocumentationContractTest.java:80   }
  MessagingDocumentationContractTest.java:81 
  MessagingDocumentationContractTest.java:82   @Test
  그 파일에서 제한·selectable·assemblable 을 담은 줄 : 0 개

# 이 상태를 적는 문서
  src/messaging/CLAUDE.md:56 - 대부분의 leaf는 `runtime_memberships`에 `app-bootstrap`을 갖는다. **배포된 아티팩트가 싣고 있고,
  src/messaging/CLAUDE.md:57   master switch `app.messaging.enabled`로 켜진다.** 기본값은 `false`다.
  src/messaging/CLAUDE.md:58 - 여전히 비어 있는 leaf(experimental transport, 일부 codec, bridge, testkit)만이 build-only /
  src/messaging/CLAUDE.md:59   incubating이다.
  src/messaging/CLAUDE.md:60 - **런타임에 있다는 것과 자격이 증명됐다는 것은 다르다.** Kafka publish 경로만 실브로커 round trip으로
  src/messaging/CLAUDE.md:61   자격 검증됐고(`MessagingLiveRoundTripQualificationTest`), Rabbit·JDBC inbox/outbox·claim check·admin
  src/messaging/CLAUDE.md:62   plane은 shipped, inactive, unqualified다. 이 구분은
  src/messaging/CLAUDE.md:63   `MessagingMembershipQualificationTest`가 문서가 아니라 테스트로 붙들고 있다.
  docs/reviews/2026-08-14-messaging-module-code-review.md:444  `RabbitChannelPublisher` contract와 `RabbitPublishMapper`는 이를 보장하지 않고 production 구현도 없다.
