# evidence 256 — messaging-provider-selection-and-rabbit-transport
# revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5
# cwd: /shared/codebase/clean-architecture-backend-template/src/messaging
# command: sed -n "1,80p" messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/MessagingProviderSelection.java; echo; echo ---RabbitChannelPublisher implementations---; grep -rn "implements RabbitChannelPublisher" --include=*.java .; echo "(no matches == no implementation anywhere)"; echo; echo ---MessagingTransport beans in the starter---; grep -rn "MessagingTransport" messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/RabbitMessagingAutoConfiguration.java messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java; echo; echo ---support-matrix.md Rabbit row---; grep -n "RabbitMQ" ../../docs/messaging/support-matrix.md | head -5
# ---- raw output ----
package dev.caskeleton.messaging.autoconfigure;

import java.util.List;
import java.util.Map;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

/**
 * Selects exactly one transport, from a closed registry, by name (MSG-INT-004).
 *
 * <p>Selection used to be {@code @ConditionalOnClass}: Kafka assembled because a Kafka class was on
 * the classpath and Rabbit because a Rabbit class was, so an application with both libraries — one
 * transitive dependency is enough — assembled both and published through whichever bean won a
 * {@code @ConditionalOnMissingBean} race. Nothing failed; the message simply went somewhere nobody
 * chose.
 *
 * <p>Three outcomes are startup errors now rather than silences:
 *
 * <ul>
 *   <li>a broker id outside the registry — the operator set something the platform cannot honour;
 *   <li>a registered broker whose client library is absent — a deployment that asked for Kafka and
 *       shipped without it is told so, instead of starting with no publisher at all;
 *   <li>a blank broker while messaging is on — the master switch says yes and nothing says what to.
 * </ul>
 *
 * <p>The provider configurations carry no condition of their own any more. Each is reached only
 * along the path this class chose, which is what makes "which transport is active" answerable by
 * reading one property rather than by resolving a dependency graph.
 */
@Configuration(proxyBeanMethods = false)
@Import(MessagingProviderSelection.Selector.class)
public class MessagingProviderSelection {

  /** Broker id to the client class that must be present for it to be honoured. */
  static final Map<String, String> REGISTERED_BROKERS =
      Map.of(
          "kafka", "org.apache.kafka.clients.producer.Producer",
          "rabbit", "com.rabbitmq.client.Channel");

  /** Broker id to the configuration that assembles it. */
  static final Map<String, Class<?>> PROVIDER_CONFIGURATIONS =
      Map.of(
          "kafka", KafkaMessagingAutoConfiguration.class,
          "rabbit", RabbitMessagingAutoConfiguration.class);

  /**
   * Broker ids that ship without a {@code MessagingTransport}, and why.
   *
   * <p>Registered is not the same as assemblable. The Rabbit leaf ships its profile validator,
   * failure classifier and security configurer, and no transport — the native channel bridge those
   * would sit on has only test implementations. Selecting it therefore produced an unsatisfied
   * dependency on {@code MessagingTransport} deep inside the core configuration, which tells an
   * operator that a bean is missing rather than that the transport they chose is not finished.
   *
   * <p>Refused here instead, next to the other selection errors, so the message names the state the
   * deployment is actually in. An entry leaves this map on the day its transport does exist.
   */
  static final Map<String, String> BROKERS_WITHOUT_A_TRANSPORT =
      Map.of(
          "rabbit",
          "the Rabbit adapter ships its validators and security configuration but no "
              + "MessagingTransport: its native channel publisher is not implemented, so a publish "
              + "has nothing to travel on");

  /** The registered broker ids, so a message can tell an operator what is available. */
  public static List<String> registeredBrokerIds() {
    return REGISTERED_BROKERS.keySet().stream().sorted().toList();
  }

  /**
   * The registered broker ids that actually assemble a transport.
   *
   * @return the ids a deployment can select today
   */
  public static List<String> assemblableBrokerIds() {

---RabbitChannelPublisher implementations---
(no matches == no implementation anywhere)

---MessagingTransport beans in the starter---
messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:163:  @ConditionalOnMissingBean(dev.caskeleton.messaging.transport.MessagingTransport.class)
messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:164:  public dev.caskeleton.messaging.kafka.KafkaMessagingTransport messagingKafkaTransport(
messaging-spring-boot-starter/src/main/java/dev/caskeleton/messaging/autoconfigure/KafkaMessagingAutoConfiguration.java:168:    return new dev.caskeleton.messaging.kafka.KafkaMessagingTransport("kafka", 1L, producer);

---support-matrix.md Rabbit row---
7:> boolean이 아니라 `CertifiedEvidence`가 가진 레인 증거에서 파생된다. RabbitMQ가 Stable에서 내려온
34:| RabbitMQ | Experimental | 4.3.x | exchange/routing, publisher confirm, mandatory return, manual ACK, quorum queue, retry queue, DLQ | 장애 시나리오 레인 미실행 — 증거 없음. stream 및 특수 plugin 미지원 |
44:| Capability | Kafka | Kafka Share | RabbitMQ | Pulsar | NATS JS |
86:Kafka와 RabbitMQ가 동일한 7개 테스트를 변경 없이 통과한다. 결정적 하네스를 쓰므로
