revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
=== stable scan includes experimental ===
7-      "dev.caskeleton.adapter.outbound.persistence.cache",
8-      "dev.caskeleton.adapter.outbound.persistence.envers",
9:      "dev.caskeleton.adapter.outbound.persistence.experimental",
10-      "dev.caskeleton.adapter.outbound.persistence.failure",
11-      "dev.caskeleton.adapter.outbound.persistence.hibernate",
--
30-      "dev.caskeleton.adapter.outbound.persistence.cache",
31-      "dev.caskeleton.adapter.outbound.persistence.envers",
32:      "dev.caskeleton.adapter.outbound.persistence.experimental",
33-      "dev.caskeleton.adapter.outbound.persistence.failure",
34-      "dev.caskeleton.adapter.outbound.persistence.hibernate",
=== opt-in exemptions known by scan coverage test ===
  /** Each opt-in capability: its package, its own configuration, and the switch that gates it. */
  private record OptInCapability(String packageName, Class<?> configuration, String switchPrefix) {}

  private static final List<OptInCapability> OPT_IN =
      List.of(
          new OptInCapability(
              PersistenceJpaConfig.OPT_IN_NOTIFICATION_PACKAGE,
              NotificationJpaPersistenceConfig.class,
              "ca-skeleton.notification.platform"),
          new OptInCapability(
              PersistenceJpaConfig.OPT_IN_FILESERVER_PACKAGE,
              FileserverJpaPersistenceConfig.class,
              "app.fileserver-platform"));

  /** Sub-packages that hold no JPA entity or repository and therefore need no scan. */
  private static final Set<String> NOT_SCANNED =
      Set.of(
          // Vendor-selection configuration only.
          "config",
          "h2",
          // The opt-in capabilities, each scanned by its own gated configuration instead.
          "notification",
          "fileserver");

=== current experimental JPA stereotypes ===
=== architecture rule checks type dependency, not scan strings ===
            "the testkit carries ArchUnit and Testcontainers; a production edge would put both on"
                + " every deployment's runtime classpath")
        .check(PRODUCTION_CLASSES);
  }

  @Test
  @DisplayName("the Stable platform does not depend on experimental packages")
  void stablePlatformDoesNotDependOnExperimentalPackages() {
    noClasses()
        .that()
        .resideOutsideOfPackage(ROOT + ".experimental..")
        .should()
        .dependOnClassesThat()
        .resideInAPackage(ROOT + ".experimental..")
        .as("experimental capabilities are never reachable from Stable code (plan §3)")
        .because(
            "an experimental capability may change or be withdrawn; a Stable type referencing one"
                + " gives it a Stable contract nobody agreed to")
        .check(PRODUCTION_CLASSES);
  }

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

**Tech Stack:** Stable 계획의 Java 21·Spring Boot 4.1·PostgreSQL Testcontainers 기반, PostgreSQL RLS, AbstractRoutingDataSource, tenant-specific DataSource registry, Jakarta Persistence 4.0 preview/final compatibility lane, Hibernate ORM 8 compatibility lane, PostgreSQL 19 compatibility lane.

## Global Constraints

- Stable 계획 Task 1~53이 완료되고 Release Gate가 통과한 뒤 시작한다.
- 모듈 루트는 `modules/jpa-experimental`이다.
- Experimental module은 `jpa-spring-boot-starter`의 기본 dependency가 아니다.
- 모든 기능은 `backend.jpa.experimental.*` feature flag를 요구한다.
- Tenant ID와 consistency token은 metric label에 기록하지 않는다.
- Tenant context 누락은 fail-closed다.
- `readOnly=true`만으로 replica routing하지 않는다.
- Lock query, write transaction, read-after-write pin은 primary를 사용한다.
