plugins { id 'ca.spring-library' id 'ca.spring-config' id 'java-test-fixtures' id 'ca.jpa-evidence' id 'ca.jpa-test-lanes' id 'ca.auxiliary-source-set' } // Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. // JPA persistence adapter — merged RDBMS base + PostgreSQL vendor module. // Owns JPA entities, Spring Data repositories, mappers, transaction/audit/lock/outbox port // implementations, and the vendor-neutral SPI interfaces (OutboxClaimRepository / // SqlStateErrorMapping). The PostgreSQL driver, flyway-database-postgresql dialect, and vendor // Flyway migrations live only under the .postgresql subpackage (ArchUnit keeps the base neutral). // The JPA relational persistence platform (docs/superpowers/specs/2026-08-11-jpa-persistence- // platform-design.md) models itself as 18 Stable library modules. This repository's fail-closed // module registry outranks that layout, so those modules are packages here and // JpaModuleBoundaryTest enforces the design's module dependency table. The full mapping is in // docs/jpa/repository-adaptation.md. // // The testkit is its own source set rather than part of `test` because more than one lane consumes // it and because a source set whose dependencies are declared only on the test configurations gives // the design's "no production module depends on the testkit" guarantee without a new Gradle project. auxiliarySourceSets { sourceSet('postgresqlIntegrationTest') { // 'testFixtures' as well as 'main': the integration lane consumed the testkit through the // convention's `consumedBy 'test', 'postgresqlIntegrationTest'`, and java-test-fixtures only // wires `test` on its own. The lane has to say so (ADR-BUILD-001). compilesAgainst 'main', 'testFixtures' inherits 'implementation', 'compileOnly', 'runtimeOnly', 'annotationProcessor' } sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testFixtures' } } dependencies { implementation project(':application-core') implementation project(':shared-contract') implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // feature-distributed-lock-contract: Spring Integration JDBC LockRegistry backs the // multi-instance distributedLockProvider. Version managed by Spring Boot BOM. implementation 'org.springframework.integration:spring-integration-jdbc' // Vendor (PostgreSQL): Flyway migration API + PostgreSQL driver/dialect. Used only by the // .postgresql subpackage; the RDBMS base stays vendor-neutral (PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL). implementation 'org.springframework.boot:spring-boot-starter-flyway' runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.flywaydb:flyway-database-postgresql' // Vendor (H2): the local-profile driver. Used only by the .h2 subpackage, which reaches it // through JDBC/JPA rather than by importing org.h2 types — the same shape as the PostgreSQL // driver above. Not `developmentOnly`: local is a deployable profile of this artifact, and the // vendor selector, not the packaging, decides which driver a deployment loads. runtimeOnly 'com.h2database:h2' // JPA platform observability (design §37). Micrometer's observation API already arrives with // Spring; the meter registry does not, and the platform's transaction/query/retry metrics need // it. Version managed by the Spring Boot BOM. implementation 'io.micrometer:micrometer-core' // Querydsl and Envers are Advanced opt-ins (design §4.2): the platform implements their // contracts, but the Stable runtime classpath must not carry either. compileOnly keeps them off // every deployment while still compiling the support classes; a deployment that opts in adds the // artifact itself, and the guards refuse the capability when the classes are absent. compileOnly 'com.querydsl:querydsl-jpa:5.1.0:jakarta' compileOnly 'org.hibernate.orm:hibernate-envers' testImplementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' testImplementation 'org.hibernate.orm:hibernate-envers' testImplementation libs.archunit.junit5 postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-postgresql' postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter' postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-toxiproxy' postgresqlIntegrationTestRuntimeOnly 'org.postgresql:postgresql' // Declared rather than inherited: the `testkit` source set extended testImplementation, so the // fixtures compiled against every test library this leaf declared without stating any of them // (ADR-BUILD-001). Listing them makes the fixtures' own surface reviewable. testFixturesImplementation 'jakarta.persistence:jakarta.persistence-api' testFixturesImplementation 'org.hibernate.orm:hibernate-envers' testFixturesImplementation 'org.flywaydb:flyway-core' testFixturesImplementation 'org.springframework:spring-jdbc' // The rule pack names Spring Data's Repository marker in a rule; it needs the type on the // compile classpath even though it never calls it. testFixturesImplementation 'org.springframework.data:spring-data-commons' testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api' testFixturesImplementation libs.toxiproxy.java testFixturesImplementation 'org.testcontainers:testcontainers' testFixturesImplementation 'org.testcontainers:testcontainers-postgresql' testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter' testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy' testFixturesImplementation libs.archunit.junit5 testFixturesRuntimeOnly 'org.postgresql:postgresql' // The performance lane starts its own servers: pool saturation is only observable against a // real database, because the thing being measured is what happens when every connection to it // is already held. jpaPlatformPerformanceTestImplementation 'org.testcontainers:testcontainers' jpaPlatformPerformanceTestImplementation 'org.testcontainers:testcontainers-postgresql' jpaPlatformPerformanceTestImplementation 'org.testcontainers:testcontainers-junit-jupiter' jpaPlatformPerformanceTestRuntimeOnly 'org.postgresql:postgresql' } // PostgreSQL readiness, tagged platform, and pool-contract lanes are owned by // `ca.jpa-test-lanes`. Their metadata is represented as Java records, so task names, selectors, // tags, descriptions and runtime property wiring are compiled rather than assembled from Groovy Maps. // The security lane includes the runtime-role namespace-denial method selector in that typed model. tasks.named('postgresqlSecurityBaselineIntegrationTest') { // Cross-leaf task edge: see the handoff. verifyCleanArchitectureDependencies inspects // configurations, not the task graph, so this edge is invisible to it. dependsOn project(':adapter:inbound:web').tasks.named('jpaPersistenceRedactionContractTest') } // Which JPA lanes block a release is declared by .github/workflows/jpa-release.yml. // This leaf declares the lanes and how each runs; it does not own release orchestration. // The unit lane reads three files that are not Java sources: the release registry and its two // renderings. Without declaring them, Gradle calls the lane up-to-date after a registry demotion or // a workflow edit — so the drift check that exists to catch exactly that edit never runs on it. // The unit lane reads files that are not Java sources: the release registry, its two renderings, // and the documents that describe the pool lane. Without declaring them, Gradle calls the lane // up-to-date after a registry demotion or a workflow edit — so the drift checks that exist to catch // exactly those edits never run on them. tasks.named('test') { inputs.file(rootProject.file('config/jpa/release-registry.json')) inputs.file(new File(rootProject.projectDir.parentFile, 'docs/jpa/support-matrix.md')) inputs.file(new File(rootProject.projectDir.parentFile, 'docs/jpa/repository-adaptation.md')) // The whole directory, not the two named workflows: the Experimental-major check asks whether // *some* lane records that major as its target, so adding or deleting any workflow can change // its answer. Naming files here would leave the lane that matters outside the up-to-date check. inputs.dir(new File(rootProject.projectDir.parentFile, '.github/workflows')) inputs.file(file('build.gradle')) inputs.dir(file('src/jpaPlatformPerformanceTest/java')) } // verifyJpaApiSurface — every public type this leaf exposes is a committed decision. // // The GraphQL and Mongo leaves already carried this; the largest of the three did not, so the one // public surface with the most adopters was the one nothing had an opinion about. The convention // plugin is opt-in per leaf, and opting in was simply never done here. // // This is a record, not a budget, and the distinction matters. A snapshot shrinks nothing on its // own: the GraphQL surface grew from 373 types to 398 while under one, each addition approved and // none refused. What the baseline buys is that growth is visible in review at the moment it // happens and that the number is available to argue with — not that the number cannot rise. A // ceiling the approval flag cannot lift is a separate decision nobody has taken yet. // `api` is the surface an adopter is meant to reach; everything else here is a candidate to become // internal at that point. apiSurface { label = 'Jpa' baseline = rootProject.file('../docs/architecture/jpa-api-surface.txt') description = 'JPA persistence leaf public API surface — every public top-level type in src/main/java.' rationale = [ 'A public type in a single-jar leaf is reachable from every adopter\'s code, so', 'additions are reviewed rather than discovered. `api` is the intended external', 'surface; the rest is implementation that has not been moved under an internal', 'root yet.', ] } // The JPA readiness registry describes this platform's lanes and resolves their task paths, so it // runs with this leaf's `check` rather than with all 62. The task itself is registered by // the ca.jpa-qualification plugin from build-tools, which the root applies. tasks.named('check') { dependsOn rootProject.tasks.named('verifyJpaReadinessRegistry') }