revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5

plan:
7:**Architecture:** Experimental module은 Stable `jpa-core-api` 계약만 소비하며 Stable starter에 자동 포함되지 않는다. 각 기능은 명시적 feature flag와 별도 compatibility/failure suite를 요구한다. 실험 결과가 Stable 의미론과 충돌하면 Core를 왜곡하지 않고 capability 또는 별도 profile로 유지한다.
16:- 모든 기능은 `backend.jpa.experimental.*` feature flag를 요구한다.
56:- Consumes: Stable `jpa-core-api` and explicit environment feature flags.

production gate calls:
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/ExperimentalFeatureGate.java:22:  public void requireEnabled(ExperimentalFeature feature, Map<String, Boolean> flags) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/rls/RlsTenantSessionBinder.java:51:        .requireEnabled(ExperimentalFeature.MULTITENANCY_RLS, flags);
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/schema/SchemaTenantMigrationOrchestrator.java:60:        .requireEnabled(ExperimentalFeature.MULTITENANCY_SCHEMA, flags);
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/replica/ConsistencyAwareDataSourceRouter.java:52:        .requireEnabled(ExperimentalFeature.READ_REPLICA, flags);

public constructors / direct entry points:
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/database/TenantEntityManagerFactoryRegistry.java:28:  public TenantEntityManagerFactoryRegistry(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/database/TenantDataSourceRegistry.java:23:public final class TenantDataSourceRegistry implements AutoCloseable {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/database/TenantDataSourceRegistry.java:34:  public TenantDataSourceRegistry(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/schema/SchemaMultiTenantConnectionProvider.java:22:public final class SchemaMultiTenantConnectionProvider {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/schema/SchemaMultiTenantConnectionProvider.java:30:  public SchemaMultiTenantConnectionProvider(DataSource dataSource, SchemaTenantRegistry registry) {
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/tenant/TenantEntityListenerGuard.java:23:  public TenantEntityListenerGuard(
src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/experimental/tenant/TenantAwareRepositoryGuard.java:17:public final class TenantAwareRepositoryGuard {

default-runtime refs outside experimental:
TenantDataSourceRegistry=3
TenantEntityManagerFactoryRegistry=1
SchemaMultiTenantConnectionProvider=2
TenantAwareRepositoryGuard=2
TenantEntityListenerGuard=1

entry-consent allowlist:
27- * that difference because they always ask.
28- */
29-class ExperimentalEntryConsentTest {
30-
31-  /** The behaviour-bearing entry points; the surrounding value types are not activations. */
32:  private static final List<Class<?>> ENTRY_POINTS =
33-      List.of(
34-          ConsistencyAwareDataSourceRouter.class,
35-          RlsTenantSessionBinder.class,
36-          SchemaTenantMigrationOrchestrator.class);
37-
38-  private final ExperimentalFeatureGate gate = new ExperimentalFeatureGate();
39-
40-  @Test
41-  @DisplayName("no experimental entry point can be constructed from outside its package")
42-  void noEntryPointCanBeConstructedFromOutsideItsPackage() {
43:    for (Class<?> entryPoint : ENTRY_POINTS) {
44-      for (Constructor<?> constructor : entryPoint.getDeclaredConstructors()) {
45-        assertThat(Modifier.isPublic(constructor.getModifiers()))
46-            .as(
47-                "%s has a public constructor, so one `new` bypasses the flag entirely",
48-                entryPoint.getSimpleName())
49-            .isFalse();
50-      }
51-    }
52-  }
53-
54-  @Test
55-  @DisplayName("every experimental entry point offers a gate-taking factory and nothing else")
56-  void everyEntryPointOffersAGateTakingFactory() {
57:    for (Class<?> entryPoint : ENTRY_POINTS) {
58-      assertThat(entryPoint.getDeclaredMethods())
59-          .as("%s must be obtainable, and only through the gate", entryPoint.getSimpleName())
60-          .anySatisfy(
61-              method -> {
62-                assertThat(method.getName()).isEqualTo("enabledBy");
63-                assertThat(method.getReturnType()).isEqualTo(entryPoint);
64-                assertThat(method.getParameterTypes()[0]).isEqualTo(ExperimentalFeatureGate.class);
65-              });
66-    }
67-  }
68-
69-  @Test
70-  @DisplayName("the RLS binder refuses to exist when its flag is absent")
71-  void theRlsBinderRefusesToExistWithoutItsFlag() {
72-    assertThatThrownBy(() -> RlsTenantSessionBinder.enabledBy(gate, Map.of()))
73-        .isInstanceOf(IllegalStateException.class)
74-        .hasMessageContaining(ExperimentalFeature.MULTITENANCY_RLS.property());
75-  }
76-
77-  @Test
