Implements the Stable and Experimental JPA persistence platform designs against real PostgreSQL, adapted to this repository's fail-closed 19-leaf registry. The design models the platform as 25 Gradle projects. `src/settings.gradle` throws unless the registry holds exactly 19 leaves, so the plan's modules become packages inside `:adapter:outbound:persistence-jpa` (starter in `:app-bootstrap`, testkit in its own source set). The full mapping, the renames this repository's naming gate required, and every deliberate substitution are recorded in `docs/jpa/repository-adaptation.md`. Seven Docker-backed lanes replace the plan's seven JVM test suites. Each fails closed: a lane that discovers nothing, or a container that cannot start, is an error rather than a skip. Three defects the contracts found against a real server: - `CommitFailureClassifier` treated only SQLSTATE 40003, class 08, and transport breaks as completion-unknown. A backend terminated mid-commit reports 57P01, and the commit record may already be in the WAL — so a possibly-committed transaction could be re-run. 57P01/57P02/57P03 now classify as completion-unknown. - `SchemaTenantMigrationOrchestrator` recorded `MigrateResult`'s target version, which is empty for a tenant already current, reporting migrated tenants as unmigrated during a partial rollout. It now reads the applied version back from the tenant's schema history. - `JpaStreamExecutor` checked only the declared return type for reactive publishers, and `RegisteredPostgreSqlCopyLoader` passed the COPY timeout to `SET`, which is parsed before parameter binding. `JpaModuleBoundaryTest` enforces the plan's module map as package rules; `verifyCleanArchitectureDependencies` governs edges between leaves and cannot see these. Its first assertion is that the import is non-empty, because every rule under it is a `noClasses()` rule and would pass vacuously on an empty import. Verified: 128 container tests across all seven lanes, 1183 unit tests, `:adapter:outbound:persistence-jpa:check`, `:app-bootstrap:check`, `verifyCleanArchitectureDependencies`, `verifyOneTypePerFile`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.6 KiB
JPA Persistence Platform — Support Matrix
The machine-readable source for JpaReleaseManifest. A release gate parses this file, so a version
or gate that stops being named here stops being claimed — and the build fails rather than the
document quietly drifting from the code.
Database
| Database | Support | Evidence |
|---|---|---|
| PostgreSQL 16 | Stable | full contract suite, release lane |
| PostgreSQL 17 | Stable | full contract suite, release lane |
| PostgreSQL 18 | Stable | full contract suite, release lane |
| PostgreSQL 19 | Experimental | compatibility lane only; promotion requires an ADR |
| H2 | Local convenience | never evidence of PostgreSQL behaviour |
H2 is not a second production target. It reports different SQLSTATEs for the same violation, has no
SKIP LOCKED guarantee the platform relies on, no JSONB operators, no range types, and no
concurrent index builds. A green H2 run is evidence that the code compiles and runs, and nothing
more.
Specification and provider
| Component | Stable | Experimental |
|---|---|---|
| Jakarta Persistence | 3.2 | 4.0 (lane) |
| Hibernate ORM | 7.4 declared baseline | 8 (lane) |
| Spring Boot | repository BOM | — |
The Hibernate row needs a note. The design declares 7.4 as the Stable provider; this repository's
Spring Boot BOM resolves 7.1.x. HibernateProviderPolicy holds both — the declared baseline as a
constant, the resolved version read from Hibernate itself — and driftsFromDeclaredBaseline() makes
the difference visible instead of asserting a constant against itself. See
repository-adaptation.md §4.
Capability support levels
| Capability | Level |
|---|---|
| Full-transaction retry | Stable |
| Commit completion evidence | Stable |
| Keyset pagination | Stable |
| JDBC batch | Stable |
| Flyway schema gate | Stable |
| Runtime role verification | Stable |
| Observability | Stable |
PostgreSQL native write (ON CONFLICT/RETURNING) |
Advanced |
PostgreSQL work claim (SKIP LOCKED) |
Advanced |
| PostgreSQL JSONB | Advanced |
| PostgreSQL array and range | Advanced |
| Bulk DML | Advanced |
Hibernate StatelessSession |
Advanced |
PostgreSQL COPY |
Admin (J4) |
| Hibernate second-level cache | Advanced |
| Hibernate Envers | Advanced |
| Multi-tenancy (column, RLS, schema, database) | Experimental |
| Consistency-aware read replica | Experimental |
Release gates
Each row is a way the platform could pass its tests and still be wrong in production.
| Gate | Kind | What it prevents |
|---|---|---|
postgresql-contract |
gate | a release whose only database evidence came from H2 |
completion-unknown-no-retry |
gate | automatically re-running a write that may already have committed |
osiv-disabled |
gate | lazy loading from the view layer, one query per rendered row |
flyway-validate |
gate | Hibernate mutating a deployed schema, or running against one it was not built for |
runtime-role-no-ddl |
gate | the application's own credential being able to alter or drop schema objects |
hibernate-7.4-fetch-pagination |
gate | a paged collection fetch silently reading the whole table and paginating in memory |
Explicitly unsupported
- Reactive JPA. JPA is a blocking specification; a reactive facade over it moves the blocking call onto an event loop rather than removing it.
- Hibernate as the production schema writer.
ddl-autonever mutates a deployed schema. - A platform-owned generic CRUD repository. Domains own their repositories (design §10.1).
- Automatic reconciliation of a completion-unknown transaction. The platform records; the domain resolves.