# MessagingLifecycle 전문 (67 줄)
  MessagingLifecycle.java:1 package dev.caskeleton.messaging.transport;
  MessagingLifecycle.java:2 
  MessagingLifecycle.java:3 import java.time.Duration;
  MessagingLifecycle.java:4 
  MessagingLifecycle.java:5 /**
  MessagingLifecycle.java:6  * The ordered lifecycle every messaging runtime implements.
  MessagingLifecycle.java:7  *
  MessagingLifecycle.java:8  * <p>The order in {@link ShutdownPhase} is the contract, not an implementation detail. Closing
  MessagingLifecycle.java:9  * connections before settlements have been transmitted loses the settlements, and pausing consumers
  MessagingLifecycle.java:10  * after draining lets fresh deliveries arrive into a runtime that is already shutting down. Each
  MessagingLifecycle.java:11  * adapter implements the phases; none of them chooses the order.
  MessagingLifecycle.java:12  *
  MessagingLifecycle.java:13  * <p>Implementations are driven by the Spring lifecycle rather than a JVM shutdown hook alone. A
  MessagingLifecycle.java:14  * shutdown hook runs after the context has already begun disposing beans, so a handler mid-drain
  MessagingLifecycle.java:15  * can find its datasource closed underneath it.
  MessagingLifecycle.java:16  */
  MessagingLifecycle.java:17 public interface MessagingLifecycle {
  MessagingLifecycle.java:18 
  MessagingLifecycle.java:19   /** The fixed phases of an orderly shutdown, in the order they must run. */
  MessagingLifecycle.java:20   enum ShutdownPhase {
  MessagingLifecycle.java:21     /** Refuse new publishes. Nothing in flight is affected. */
  MessagingLifecycle.java:22     STOP_PUBLISH_ADMISSION,
  MessagingLifecycle.java:23     /** Refuse to start new delivery handlers. */
  MessagingLifecycle.java:24     STOP_NEW_HANDLERS,
  MessagingLifecycle.java:25     /** Ask the broker to stop delivering. */
  MessagingLifecycle.java:26     PAUSE_CONSUMERS,
  MessagingLifecycle.java:27     /** Let running handlers finish. */
  MessagingLifecycle.java:28     DRAIN_HANDLERS,
  MessagingLifecycle.java:29     /** Transmit the settlements those handlers produced. */
  MessagingLifecycle.java:30     FLUSH_SETTLEMENTS,
  MessagingLifecycle.java:31     /** Wait for outstanding producer confirms so publishes are not left ambiguous. */
  MessagingLifecycle.java:32     AWAIT_PRODUCER_CONFIRMS,
  MessagingLifecycle.java:33     /** Return outbox leases so another relay can claim the rows immediately. */
  MessagingLifecycle.java:34     RELEASE_OUTBOX_LEASES,
  MessagingLifecycle.java:35     /** Close connections and channels. */
  MessagingLifecycle.java:36     CLOSE_CONNECTIONS
  MessagingLifecycle.java:37   }
  MessagingLifecycle.java:38 
  MessagingLifecycle.java:39   /** The default drain budget from the design. */
  MessagingLifecycle.java:40   Duration DEFAULT_DRAIN_DEADLINE = Duration.ofSeconds(30);
  MessagingLifecycle.java:41 
  MessagingLifecycle.java:42   /**
  MessagingLifecycle.java:43    * Starts the runtime.
  MessagingLifecycle.java:44    *
  MessagingLifecycle.java:45    * @throws IllegalStateException when the runtime was already started
  MessagingLifecycle.java:46    */
  MessagingLifecycle.java:47   void start();
  MessagingLifecycle.java:48 
  MessagingLifecycle.java:49   /**
  MessagingLifecycle.java:50    * Runs the shutdown phases in order, bounded by the drain deadline.
  MessagingLifecycle.java:51    *
  MessagingLifecycle.java:52    * <p>Work still running at the deadline is abandoned <em>unsettled</em> so the broker redelivers
  MessagingLifecycle.java:53    * it. Unconfirmed publishes are recorded as ambiguous rather than as successes.
  MessagingLifecycle.java:54    *
  MessagingLifecycle.java:55    * @param drainDeadline how long in-flight work may take
  MessagingLifecycle.java:56    * @return the phase that was still running when the deadline passed, or {@link
  MessagingLifecycle.java:57    *     ShutdownPhase#CLOSE_CONNECTIONS} when the shutdown completed
  MessagingLifecycle.java:58    */
  MessagingLifecycle.java:59   ShutdownPhase shutdown(Duration drainDeadline);
  MessagingLifecycle.java:60 
  MessagingLifecycle.java:61   /**
  MessagingLifecycle.java:62    * Reports whether the runtime is running and accepting work.
  MessagingLifecycle.java:63    *
  MessagingLifecycle.java:64    * @return true between {@link #start()} and the first shutdown phase
  MessagingLifecycle.java:65    */
  MessagingLifecycle.java:66   boolean isRunning();
  MessagingLifecycle.java:67 }
  ShutdownPhase 상수 개수 : 8 개
  인터페이스가 요구하는 메서드 :
    :40  Duration DEFAULT_DRAIN_DEADLINE = Duration.ofSeconds(30);
    :47  void start();
    :59  ShutdownPhase shutdown(Duration drainDeadline);
    :66  boolean isRunning();

# MessagingLifecycleTest 전문 (59 줄)
  MessagingLifecycleTest.java:1 package dev.caskeleton.messaging.transport;
  MessagingLifecycleTest.java:2 
  MessagingLifecycleTest.java:3 import static org.assertj.core.api.Assertions.assertThat;
  MessagingLifecycleTest.java:4 
  MessagingLifecycleTest.java:5 import dev.caskeleton.messaging.transport.MessagingLifecycle.ShutdownPhase;
  MessagingLifecycleTest.java:6 import java.time.Duration;
  MessagingLifecycleTest.java:7 import java.util.List;
  MessagingLifecycleTest.java:8 import org.junit.jupiter.api.Test;
  MessagingLifecycleTest.java:9 
  MessagingLifecycleTest.java:10 class MessagingLifecycleTest {
  MessagingLifecycleTest.java:11 
  MessagingLifecycleTest.java:12   @Test
  MessagingLifecycleTest.java:13   void publishAdmissionClosesBeforeConsumersArePaused() {
  MessagingLifecycleTest.java:14     List<ShutdownPhase> order = List.of(ShutdownPhase.values());
  MessagingLifecycleTest.java:15 
  MessagingLifecycleTest.java:16     assertThat(order.indexOf(ShutdownPhase.STOP_PUBLISH_ADMISSION))
  MessagingLifecycleTest.java:17         .isLessThan(order.indexOf(ShutdownPhase.PAUSE_CONSUMERS));
  MessagingLifecycleTest.java:18   }
  MessagingLifecycleTest.java:19 
  MessagingLifecycleTest.java:20   @Test
  MessagingLifecycleTest.java:21   void handlersDrainBeforeTheirSettlementsAreFlushed() {
  MessagingLifecycleTest.java:22     List<ShutdownPhase> order = List.of(ShutdownPhase.values());
  MessagingLifecycleTest.java:23 
  MessagingLifecycleTest.java:24     assertThat(order.indexOf(ShutdownPhase.DRAIN_HANDLERS))
  MessagingLifecycleTest.java:25         .as("flushing before the handlers finish would lose the settlements they produce")
  MessagingLifecycleTest.java:26         .isLessThan(order.indexOf(ShutdownPhase.FLUSH_SETTLEMENTS));
  MessagingLifecycleTest.java:27   }
  MessagingLifecycleTest.java:28 
  MessagingLifecycleTest.java:29   @Test
  MessagingLifecycleTest.java:30   void connectionsCloseLastOfAll() {
  MessagingLifecycleTest.java:31     ShutdownPhase[] order = ShutdownPhase.values();
  MessagingLifecycleTest.java:32 
  MessagingLifecycleTest.java:33     assertThat(order[order.length - 1])
  MessagingLifecycleTest.java:34         .as("every earlier phase needs the connection that this one closes")
  MessagingLifecycleTest.java:35         .isEqualTo(ShutdownPhase.CLOSE_CONNECTIONS);
  MessagingLifecycleTest.java:36   }
  MessagingLifecycleTest.java:37 
  MessagingLifecycleTest.java:38   @Test
  MessagingLifecycleTest.java:39   void producerConfirmsAreAwaitedBeforeTheConnectionGoesAway() {
  MessagingLifecycleTest.java:40     List<ShutdownPhase> order = List.of(ShutdownPhase.values());
  MessagingLifecycleTest.java:41 
  MessagingLifecycleTest.java:42     assertThat(order.indexOf(ShutdownPhase.AWAIT_PRODUCER_CONFIRMS))
  MessagingLifecycleTest.java:43         .as("a confirm that arrives after close cannot be observed, leaving the publish ambiguous")
  MessagingLifecycleTest.java:44         .isLessThan(order.indexOf(ShutdownPhase.CLOSE_CONNECTIONS));
  MessagingLifecycleTest.java:45   }
  MessagingLifecycleTest.java:46 
  MessagingLifecycleTest.java:47   @Test
  MessagingLifecycleTest.java:48   void outboxLeasesAreReleasedBeforeClosing() {
  MessagingLifecycleTest.java:49     List<ShutdownPhase> order = List.of(ShutdownPhase.values());
  MessagingLifecycleTest.java:50 
  MessagingLifecycleTest.java:51     assertThat(order.indexOf(ShutdownPhase.RELEASE_OUTBOX_LEASES))
  MessagingLifecycleTest.java:52         .isLessThan(order.indexOf(ShutdownPhase.CLOSE_CONNECTIONS));
  MessagingLifecycleTest.java:53   }
  MessagingLifecycleTest.java:54 
  MessagingLifecycleTest.java:55   @Test
  MessagingLifecycleTest.java:56   void theDefaultDrainDeadlineMatchesTheDesign() {
  MessagingLifecycleTest.java:57     assertThat(MessagingLifecycle.DEFAULT_DRAIN_DEADLINE).isEqualTo(Duration.ofSeconds(30));
  MessagingLifecycleTest.java:58   }
  MessagingLifecycleTest.java:59 }

# 그 두 이름을 저장소 어디서 쓰는가
  pathspec 없이 이름이 나오는 파일 전부 :
    docs/superpowers/plans/2026-08-10-messaging-platform-implementation-plan.md
    src/messaging/messaging-transport-spi/src/main/java/dev/caskeleton/messaging/transport/MessagingLifecycle.java
    src/messaging/messaging-transport-spi/src/test/java/dev/caskeleton/messaging/transport/MessagingLifecycleTest.java
  implements MessagingLifecycle 를 가진 파일 : 0 개
  [자기시험] implements MessagingLifecycleZZZ 를 가진 파일 : 0 개 (0 이어야 정상)
  [대조] implements MessagingTransport 를 가진 파일 : 6 개 (0 이면 검색식이 깨진 것)
  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 {

# DEFAULT_DRAIN_DEADLINE 이라는 이름을 가진 선언과 그 사용처
  messaging/messaging-outbox-jdbc-postgresql · main · OutboxRelayWorker.java:35    public static final Duration DEFAULT_DRAIN_DEADLINE = Duration.ofSeconds(30);
  messaging/messaging-outbox-jdbc-postgresql · main · OutboxRelayWorker.java:129      stop(DEFAULT_DRAIN_DEADLINE);
  messaging/messaging-transport-spi · main · DefaultMessagingRuntimeRegistry.java:28    private static final Duration DEFAULT_DRAIN_DEADLINE = Duration.ofSeconds(30);
  messaging/messaging-transport-spi · main · DefaultMessagingRuntimeRegistry.java:36      this(DEFAULT_DRAIN_DEADLINE);
  messaging/messaging-transport-spi · main · MessagingLifecycle.java:40    Duration DEFAULT_DRAIN_DEADLINE = Duration.ofSeconds(30);
  messaging/messaging-transport-spi · test · MessagingLifecycleTest.java:57      assertThat(MessagingLifecycle.DEFAULT_DRAIN_DEADLINE).isEqualTo(Duration.ofSeconds(30));

# 프로퍼티로 들어오는 드레인 마감과 그것을 받는 자리
  messaging/messaging-spring-boot-starter · main · MessagingCoreAutoConfiguration.java:192      return new DefaultMessagingRuntimeRegistry(properties.getShutdown().getDrainDeadline());
  messaging/messaging-spring-boot-starter · main · MessagingCoreAutoConfiguration.java:204      return new GracefulShutdownCoordinator(properties.getShutdown().getDrainDeadline());
  messaging/messaging-spring-boot-starter · main · MessagingCoreAutoConfiguration.java:243          admission, drain, properties.getShutdown().getDrainDeadline());
  messaging/messaging-spring-boot-starter · main · MessagingSettings.java:281      private Duration drainDeadline = Duration.ofSeconds(30);
  messaging/messaging-spring-boot-starter · test · MessagingAutoConfigurationTest.java:83                assertThat(properties.getShutdown().getDrainDeadline())

# 오늘 종료를 담당하는 구현
  messaging/messaging-spring-boot-starter · main · MessagingOutboxRelayLifecycle.java:23  public final class MessagingOutboxRelayLifecycle implements SmartLifecycle {
  messaging/messaging-spring-boot-starter · main · MessagingShutdownLifecycle.java:30  public final class MessagingShutdownLifecycle implements SmartLifecycle {
  messaging/messaging-spring-boot-starter · test · MessagingOutboxRelayLifecycleTest.java:38  class MessagingOutboxRelayLifecycleTest {
  messaging/messaging-spring-boot-starter · test · MessagingShutdownLifecycleTest.java:25  class MessagingShutdownLifecycleTest {

# 열거형 상수 순서를 바꾸면 무엇이 깨지는가 (레포 사본을 /tmp 에서 컴파일)
  사본을 그대로 컴파일했을 때
    통과 6 · 실패 0
  CLOSE_CONNECTIONS 를 첫 상수로 옮겼을 때
    통과 3 · 실패 3
      깨진 시험 : connectionsCloseLastOfAll
      깨진 시험 : outboxLeasesAreReleasedBeforeClosing
      깨진 시험 : producerConfirmsAreAwaitedBeforeTheConnectionGoesAway
  레포가 수정되지 않았는지 : 0 건 변경
