Command: static reachability/wiring/export/package evidence for hibernate, springdata, querydsl Working directory: /shared/codebase/clean-architecture-backend-template Executed at: 2026-08-29T08:22:05Z Source revision: a24ece9cf797f7ea647e33bf846b115208ed1ba5 Observation boundary: Tracked production Java/static configuration only. Zero references do not exclude reflection/external adopters; package export rules and test/integration consumers are shown separately. --- stdout/stderr --- === package denominator === hibernate main count= 22 loc= 1111 hibernate test count= 2 loc= 149 springdata main count= 17 loc= 1028 springdata test count= 8 loc= 598 querydsl main count= 3 loc= 139 querydsl test count= 1 loc= 53 === key implementation production refs === --- NamedStatementInspector src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/NamedStatementInspector.java:20:public final class NamedStatementInspector implements StatementInspector { --- HibernateBatchConfigurationGuard src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/HibernateBatchConfigurationGuard.java:23:public final class HibernateBatchConfigurationGuard { --- HibernateJpaBatchExecutor src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/HibernateJpaBatchExecutor.java:22:public final class HibernateJpaBatchExecutor implements JpaBatchExecutor { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/HibernateJpaBatchExecutor.java:27: public HibernateJpaBatchExecutor( --- JpaBatchProfileRegistry src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/JpaBatchProfileRegistry.java:14:public final class JpaBatchProfileRegistry { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/JpaBatchProfileRegistry.java:18: public JpaBatchProfileRegistry(Map profiles) { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/JpaBatchProfileRegistry.java:35: public static JpaBatchProfileRegistry of(JpaBatchProfile... profiles) { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/batch/JpaBatchProfileRegistry.java:42: return new JpaBatchProfileRegistry(byName); --- HibernateBulkDmlExecutor src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/bulk/HibernateBulkDmlExecutor.java:28:public final class HibernateBulkDmlExecutor implements BulkDmlExecutor { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/bulk/HibernateBulkDmlExecutor.java:38: public HibernateBulkDmlExecutor( --- HibernateStatelessSessionRunner src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/stateless/HibernateStatelessSessionRunner.java:23:public final class HibernateStatelessSessionRunner implements StatelessSessionRunner { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/stateless/HibernateStatelessSessionRunner.java:28: public HibernateStatelessSessionRunner( --- EntityGraphCatalog src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/EntityGraphCatalog.java:20:public final class EntityGraphCatalog { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/EntityGraphCatalog.java:25: public EntityGraphCatalog( src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/EntityGraphCatalog.java:40: public static EntityGraphCatalog empty() { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/EntityGraphCatalog.java:41: return new EntityGraphCatalog(Map.of()); src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/FetchPlanApplier.java:30: private final EntityGraphCatalog catalog; src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/FetchPlanApplier.java:33: public FetchPlanApplier(EntityGraphCatalog catalog, EntityManager entityManager) { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/FetchPlanApplier.java:61: public EntityGraphCatalog catalog() { --- FetchPlanApplier src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/FetchPlanApplier.java:22:public final class FetchPlanApplier { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/FetchPlanApplier.java:33: public FetchPlanApplier(EntityGraphCatalog catalog, EntityManager entityManager) { --- JpaKeysetQuerySupport src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaKeysetQuerySupport.java:20:public final class JpaKeysetQuerySupport { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaKeysetQuerySupport.java:24: public JpaKeysetQuerySupport(KeysetSliceAssembler assembler) { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaKeysetQuerySupport.java:29: public static JpaKeysetQuerySupport standard() { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaKeysetQuerySupport.java:30: return new JpaKeysetQuerySupport(new KeysetSliceAssembler()); --- JpaRepositoryFragmentSupport src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaRepositoryFragmentSupport.java:24:public abstract class JpaRepositoryFragmentSupport { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaRepositoryFragmentSupport.java:31: protected JpaRepositoryFragmentSupport(EntityManager entityManager) { --- JpaStreamExecutor src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaStreamExecutor.java:27:public final class JpaStreamExecutor { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaStreamExecutor.java:41: public JpaStreamExecutor(QueryObservation observation) { --- KeysetPredicateBuilder src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/KeysetPredicateBuilder.java:32:public final class KeysetPredicateBuilder { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/KeysetPredicateBuilder.java:34: private KeysetPredicateBuilder() {} --- SpecificationPolicy src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/SpecificationPolicy.java:18:public final class SpecificationPolicy { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/SpecificationPolicy.java:23: private SpecificationPolicy() {} --- QuerydslJpaSupport src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/querydsl/QuerydslJpaSupport.java:21:public final class QuerydslJpaSupport { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/querydsl/QuerydslJpaSupport.java:28: public QuerydslJpaSupport(JPAQueryFactory queryFactory) { === querydsl lock/declarations === 43-com.puppycrawl.tools:checkstyle:13.5.0=checkstyle 44-com.querydsl:querydsl-core:5.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath 45:com.querydsl:querydsl-jpa:5.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath 46-com.sun.istack:istack-commons-runtime:4.1.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath 47-com.tngtech.archunit:archunit-junit5-api:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath 69: compileOnly 'com.querydsl:querydsl-jpa:5.1.0:jakarta' 72: testImplementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' === statement inspector configuration === src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/hibernate/NamedStatementInspector.java:20:public final class NamedStatementInspector implements StatementInspector { src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/springdata/JpaRepositoryFragmentSupport.java:66: * Runs {@code work} with {@code name} bound as the statement-inspector identity. === export lists === /** * The packages a leaf outside this one may import from. * *

An export list, because 318 of 324 production files declare a public top-level type and * "public" inside one jar is public to everybody who has the jar. The composition root imported * twenty-two notification implementation types by name, which turned each of them into a * cross-leaf source contract: moving one to another package, or reducing its visibility, broke * the root's compile. The notification wiring is now a facade the leaf owns, and this names what * the rest of the repository may reach for. * *

Everything absent from this list is internal. It may still be {@code public} — Java has no * finer granularity inside one jar — but importing it from another leaf is a boundary violation * this test reports, not a compile error the language provides. */ private static final Set EXPORTED_PACKAGES = Set.of( // The contracts other leaves are meant to speak. "api", // Capability configuration facades, each assembling its own capability's internals. "notification.configuration", // Cross-cutting seams a composition root legitimately wires. "transaction", "security", "observation", "migration", "hibernate", "fileserver", "failure", "config"); private static final JavaClasses PRODUCTION_CLASSES = // Future NoSQL adapter modules (for example adapter-persistence-mongodb) must be added as // sibling modules that implement application/domain ports directly. When such a module exists, // add an ArchUnit rule forbidding jakarta.persistence.., org.hibernate.., // org.springframework.data.jpa.., org.flywaydb.., and // dev.caskeleton.adapter.outbound.persistence.postgresql.. // dependencies from that module. /** * The composition root reaches only the persistence leaf's exported surface. * *

318 of that leaf's 324 production files declare a public top-level type, and {@code public} * inside one jar is public to everyone holding the jar. The root imported twenty-two notification * implementation types by name — every store, every repository, the record mapper — which made * each of them a cross-leaf source contract: moving one to another package, or reducing its * visibility, broke this module's compile. The notification wiring is a facade the leaf owns now, * and this keeps the surface from re-growing one convenient import at a time. * *

The list is the leaf's export allowlist, mirrored here because this is the consumer side of * the same boundary. {@code JpaModuleBoundaryTest} owns the definition. */ @ArchTest static final ArchRule BOOTSTRAP_USES_ONLY_THE_PERSISTENCE_EXPORT_SURFACE = noClasses() .that() .resideInAPackage("dev.caskeleton.bootstrap..") .should() .dependOnClassesThat( new com.tngtech.archunit.base.DescribedPredicate<>( "a persistence type outside the exported surface") { private static final java.util.Set EXPORTED = java.util.Set.of( "api", "notification.configuration", "transaction", "security", "observation", "migration", "hibernate", "fileserver", "failure", "config", // The two vendor entry points. A vendor configuration imports the core JPA // config rather than the reverse, so there is no single internal entry the // composition root could gate instead — inverting the import to make one // produced a package cycle. Admitting exactly these two, and no other // vendor // internals, is the narrower price. "postgresql", "h2"); private static final String ROOT = "dev.caskeleton.adapter.outbound.persistence."; @Override public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { String packageName = candidate.getPackageName(); if (!packageName.startsWith(ROOT)) { return false; } String remainder = packageName.substring(ROOT.length()); return EXPORTED.stream() .noneMatch( exported -> remainder.equals(exported) || remainder.startsWith(exported + ".")); } }) .as( "app-bootstrap imports only the persistence leaf's exported packages: every other" + " public type there is internal, and importing one freezes it as a contract.") .allowEmptyShould(true); /** * A persistence adapter stores what a use case decided; it does not make the decision. * *

{@code JpaNotificationRequestStore} imported {@code NotificationSubmissionService} and Exit code: 0