diff --git a/src/adapter/inbound/web/build.gradle b/src/adapter/inbound/web/build.gradle
index ee75f0c5..ea9fb27f 100644
--- a/src/adapter/inbound/web/build.gradle
+++ b/src/adapter/inbound/web/build.gradle
@@ -1,3 +1,6 @@
+// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
+apply plugin: 'java-test-fixtures'
+
// The inbound HTTP API execution platform design models itself as 23 Stable Gradle modules under
// modules/web. This repository's fail-closed module registry outranks that layout, so those modules
// are packages here and WebModuleBoundaryTest enforces the design's module dependency table. The
@@ -14,14 +17,14 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.session:spring-session-core'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
- implementation('org.openapitools:jackson-databind-nullable:0.2.6') {
+ implementation(libs.jackson.databind.nullable) {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
}
// feature-api-contract-baseline D10: OpenAPI producer. springdoc exposes the
// running app's machine-readable contract at /v3/api-docs (OAS 3.1, generated —
// never a hand-maintained stale schema). The release-blocking drift gate is
// owned by feature-contract-verification-test-suite (planned).
- implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0'
+ implementation libs.springdoc.openapi.starter.webmvc.api
// The two Advanced representations, compile-only on purpose. They were `implementation` first,
// on the reasoning that a missing backend would surface as a NoClassDefFoundError at the first
// request that negotiated one. That reasoning was wrong about what the jars do: Spring Boot's
@@ -65,13 +68,12 @@ tasks.named('test') {
// tests and by the composition root. A rule pack that only its own fixture tests import is verified
// as library code and applied to nothing — the shape the JPA testkit had to be corrected out of.
strictTestLanes {
- sourceSet('testkit') { compilesAgainst 'main' }
// The Jetty compatibility lane is its own source set because it needs a different embedded
// server on the classpath. Two servers in one source set means Spring Boot picks one and the
// "Jetty" lane silently runs on Tomcat — a compatibility matrix that certifies the same
// container twice.
sourceSet('jettyCompatTest') {
- compilesAgainst 'main', 'testkit'
+ compilesAgainst 'main', 'testFixtures'
inherits 'implementation'
}
// Reactor Netty is the Stable WebFlux server baseline and it cannot share a source set with
@@ -85,11 +87,11 @@ strictTestLanes {
// developer's `check` depend on a container runtime, and the usual outcome of that is an
// @Disabled that nobody notices has been there for months.
sourceSet('nginxProxyTest') {
- compilesAgainst 'main', 'testkit'
+ compilesAgainst 'main', 'testFixtures'
inherits 'implementation'
}
sourceSet('webfluxContractTest') {
- compilesAgainst 'main', 'testkit'
+ compilesAgainst 'main', 'testFixtures'
// Inherits nothing. The default is to extend `testImplementation`, which extends the leaf's
// own `implementation` and therefore carries spring-boot-starter-web — and with Tomcat on
// the classpath Boot deduces a servlet application, starts a servlet container, and the
@@ -98,16 +100,36 @@ strictTestLanes {
}
}
-testkitPublisher {
- consumedBy 'test'
- publishAs 'webTestkit'
-}
-
-// ArchUnit is declared after the testkit source set exists, because `testkitImplementation` is
+// ArchUnit is declared after the testkit source set exists, because `testFixturesImplementation` is
// created by that declaration.
dependencies {
- testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
+ // The fixtures speak the application's ports, so they compile against it directly. Under the
+ // old source set this arrived through testImplementation inheritance.
+ testFixturesImplementation project(':application-core')
+ testFixturesImplementation project(':shared-contract')
+
+ // 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.servlet:jakarta.servlet-api'
+ testFixturesImplementation 'jakarta.validation:jakarta.validation-api'
+ testFixturesImplementation 'org.assertj:assertj-core'
+ testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api'
+ testFixturesImplementation 'org.springframework:spring-web'
+ testFixturesImplementation 'org.springframework:spring-webmvc'
+ testFixturesImplementation 'org.springframework:spring-context'
+ testFixturesImplementation 'org.springframework:spring-core'
+ testFixturesImplementation 'org.springframework.boot:spring-boot'
+ testFixturesImplementation 'org.springframework.boot:spring-boot-test'
+ testFixturesImplementation 'org.springframework.boot:spring-boot-web-server'
+ testFixturesImplementation 'org.springframework.boot:spring-boot-autoconfigure'
+ testFixturesImplementation 'org.springframework.security:spring-security-config'
+ testFixturesImplementation 'tools.jackson.core:jackson-databind'
+ testFixturesImplementation 'org.springframework.security:spring-security-core'
+ testFixturesImplementation 'org.springframework.security:spring-security-web'
+ testFixturesImplementation 'io.projectreactor:reactor-core'
+ testFixturesImplementation libs.archunit.junit5
+ testImplementation libs.archunit.junit5
// Jetty replaces Tomcat for this lane only. The exclusion is what makes the lane mean
// something: with both on the classpath Boot starts Tomcat and the lane certifies nothing.
diff --git a/src/adapter/inbound/web/gradle.lockfile b/src/adapter/inbound/web/gradle.lockfile
index 6e6bc297..09461521 100644
--- a/src/adapter/inbound/web/gradle.lockfile
+++ b/src/adapter/inbound/web/gradle.lockfile
@@ -2,64 +2,64 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
-ch.qos.logback:logback-classic:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-ch.qos.logback:logback-core:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+ch.qos.logback:logback-classic:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+ch.qos.logback:logback-core:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
com.fasterxml.woodstox:woodstox-core:7.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml:classmate:1.7.3=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.fasterxml:classmate:1.7.3=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
com.github.docker-java:docker-java-api:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
com.github.docker-java:docker-java-transport-zerodep:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
com.github.docker-java:docker-java-transport:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
-com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
com.github.spotbugs:spotbugs:4.10.2=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
-com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.auto:auto-common:1.2.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.auto:auto-common:1.2.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs
com.google.code.gson:gson:2.13.2=spotbugs
-com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
com.google.errorprone:error_prone_annotations:2.41.0=spotbugs
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
-com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.guava:guava:33.5.0-jre=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.guava:guava:33.5.0-jre=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
com.google.guava:guava:33.6.0-jre=checkstyle
-com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.10.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
-com.tngtech.archunit:archunit-junit5-api:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-api:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
commons-codec:commons-codec:1.19.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
commons-io:commons-io:2.20.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
commons-io:commons-io:2.21.0=spotbugs
-commons-logging:commons-logging:1.3.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+commons-logging:commons-logging:1.3.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
info.picocli:picocli:4.7.7=checkstyle
-io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-io.micrometer:micrometer-commons:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-io.micrometer:micrometer-observation:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+io.micrometer:micrometer-commons:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+io.micrometer:micrometer-observation:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
io.netty:netty-buffer:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
io.netty:netty-codec-base:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
io.netty:netty-codec-classes-quic:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
@@ -83,27 +83,27 @@ io.netty:netty-transport-native-unix-common:4.2.17.Final=webfluxContractTestComp
io.netty:netty-transport:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
io.projectreactor.netty:reactor-netty-core:1.3.7=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
io.projectreactor.netty:reactor-netty-http:1.3.7=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-io.projectreactor:reactor-core:3.8.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+io.projectreactor:reactor-core:3.8.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
io.projectreactor:reactor-test:3.8.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
jakarta.enterprise:jakarta.enterprise.lang-model:4.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
jakarta.inject:jakarta.inject-api:2.0.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
jakarta.interceptor:jakarta.interceptor-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
-jakarta.servlet:jakarta.servlet-api:6.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
+jakarta.servlet:jakarta.servlet-api:6.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath
jakarta.transaction:jakarta.transaction-api:2.0.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
-jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
jakarta.websocket:jakarta.websocket-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
jakarta.websocket:jakarta.websocket-client-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
-jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+javax.inject:javax.inject:1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
jaxen:jaxen:2.0.6=spotbugs
net.bytebuddy:byte-buddy-agent:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-net.bytebuddy:byte-buddy:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+net.bytebuddy:byte-buddy:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
net.java.dev.jna:jna:5.18.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
net.minidev:accessors-smart:2.6.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
net.minidev:json-smart:2.6.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
@@ -111,24 +111,24 @@ net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
org.antlr:antlr4-runtime:4.13.2=checkstyle
org.apache.bcel:bcel:6.12.0=spotbugs
org.apache.commons:commons-compress:1.28.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
-org.apache.commons:commons-lang3:3.20.0=checkstyle,compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-lang3:3.20.0=checkstyle,compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.commons:commons-text:1.15.0=spotbugs
org.apache.commons:commons-text:1.3=checkstyle
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
-org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.apache.logging.log4j:log4j-core:2.25.5=spotbugs
-org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
-org.apache.tomcat.embed:tomcat-embed-core:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-core:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
-org.apiguardian:apiguardian-api:1.1.2=jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
-org.assertj:assertj-core:3.27.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.apiguardian:apiguardian-api:1.1.2=jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
+org.assertj:assertj-core:3.27.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.awaitility:awaitility:4.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
@@ -167,25 +167,25 @@ org.eclipse.jetty:jetty-session:12.1.12=jettyCompatTestCompileClasspath,jettyCom
org.eclipse.jetty:jetty-util:12.1.12=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.1.12=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
org.hamcrest:hamcrest:3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
-org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.jetbrains:annotations:17.0.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
-org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jettyCompatTestAnnotationProcessor,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestAnnotationProcessor,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestAnnotationProcessor,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jettyCompatTestAnnotationProcessor,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestAnnotationProcessor,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestAnnotationProcessor,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.junit.jupiter:junit-jupiter-api:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.junit.jupiter:junit-jupiter:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.junit.platform:junit-platform-commons:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.junit.platform:junit-platform-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
+org.junit.platform:junit-platform-commons:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.junit.platform:junit-platform-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
-org.junit:junit-bom:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.junit:junit-bom:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
org.mockito:mockito-core:5.20.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,mockitoAgent,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.objenesis:objenesis:3.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath
-org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.opentest4j:opentest4j:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.opentest4j:opentest4j:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
org.osgi:org.osgi.resource:1.0.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath
@@ -196,82 +196,82 @@ org.ow2.asm:asm-tree:9.10.1=jettyCompatTestCompileClasspath,jettyCompatTestRunti
org.ow2.asm:asm-util:9.10.1=spotbugs
org.ow2.asm:asm:9.10.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,spotbugs
org.ow2.asm:asm:9.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.pcollections:pcollections:4.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
-org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.pcollections:pcollections:4.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor
+org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
org.rnorth.duct-tape:duct-tape:1.0.8=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
org.skyscreamer:jsonassert:1.5.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.slf4j:slf4j-api:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.slf4j:slf4j-api:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.18=checkstyle
-org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor
org.springframework.boot:spring-boot-http-codec:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-http-converter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-http-converter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-jetty:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
org.springframework.boot:spring-boot-netty:4.0.8=webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-reactor-netty:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-reactor:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-resttestclient:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-servlet:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-servlet:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-starter-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-jetty-runtime:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-jetty:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath
-org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-reactor-netty:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-starter-web:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-starter-web:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webflux:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.boot:spring-boot-web-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.boot:spring-boot-web-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-webflux:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
org.springframework.boot:spring-boot-webmvc-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-webmvc:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework.security:spring-security-config:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-oauth2-jose:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-oauth2-resource-server:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-webmvc:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework.security:spring-security-config:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-oauth2-jose:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-oauth2-resource-server:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.security:spring-security-test:7.0.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-web:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.session:spring-session-core:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-aop:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-beans:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-context:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-core:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-expression:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-test:7.0.9=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-web:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-webflux:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.springframework:spring-webmvc:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-web:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.session:spring-session-core:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-aop:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-beans:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-context:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-core:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-expression:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-test:7.0.9=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-web:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-webflux:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.springframework:spring-webmvc:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.testcontainers:testcontainers-junit-jupiter:2.0.5=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
org.testcontainers:testcontainers:2.0.5=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
org.xmlunit:xmlunit-core:2.10.4=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-org.yaml:snakeyaml:2.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-tools.jackson.core:jackson-core:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
-tools.jackson.core:jackson-databind:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+org.yaml:snakeyaml:2.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+tools.jackson.core:jackson-core:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+tools.jackson.core:jackson-databind:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.dataformat:jackson-dataformat-xml:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson:jackson-bom:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
+tools.jackson:jackson-bom:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath
empty=
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java
diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java
rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java
diff --git a/src/adapter/inbound/web/src/testkit/resources/performance/web-load-profile.yaml b/src/adapter/inbound/web/src/testFixtures/resources/performance/web-load-profile.yaml
similarity index 100%
rename from src/adapter/inbound/web/src/testkit/resources/performance/web-load-profile.yaml
rename to src/adapter/inbound/web/src/testFixtures/resources/performance/web-load-profile.yaml
diff --git a/src/adapter/inbound/websocket/build.gradle b/src/adapter/inbound/websocket/build.gradle
index 93f7d9d6..8e3209da 100644
--- a/src/adapter/inbound/websocket/build.gradle
+++ b/src/adapter/inbound/websocket/build.gradle
@@ -1,3 +1,6 @@
+// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
+apply plugin: 'java-test-fixtures'
+
// Driving adapter: WebSocket (STOMP over SockJS) live-push channel (skeleton machinery, transport-only).
//
// W1 live-bus: LiveEventStompBroadcaster forwards only explicitly allowlisted, bounded primitive
@@ -41,9 +44,9 @@ dependencies {
// Pinned: no BOM manages it. The version matches what the toolchain already resolves for the
// annotation processor, so the runtime and the processor cannot drift onto two protobuf
// runtimes with different wire behaviour.
- compileOnly 'com.google.protobuf:protobuf-java:4.33.2'
+ compileOnly libs.protobuf.java
testImplementation 'tools.jackson.dataformat:jackson-dataformat-cbor'
- testImplementation 'com.google.protobuf:protobuf-java:4.33.2'
+ testImplementation libs.protobuf.java
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}
@@ -52,7 +55,6 @@ dependencies {
// tests and available to the composition root. A rule pack that only its own fixture tests import
// is verified as library code and applied to nothing.
strictTestLanes {
- sourceSet('testkit') { compilesAgainst 'main' }
// Jetty replaces Tomcat for this lane only. With both on one classpath Boot starts Tomcat and
// the lane certifies the same container twice — which for a WebSocket matters more than for
// HTTP, because upgrade handling, close-frame timing and idle handling are all container code.
@@ -60,30 +62,35 @@ strictTestLanes {
// only lane that needs Docker; folded into `test`, every developer's `check` would depend on a
// container runtime and the usual end of that is a disabled test nobody notices.
sourceSet('nginxWebSocketTest') {
- compilesAgainst 'main', 'testkit'
+ compilesAgainst 'main', 'testFixtures'
inherits 'implementation'
}
sourceSet('jettyWebSocketTest') {
- compilesAgainst 'main', 'testkit'
+ compilesAgainst 'main', 'testFixtures'
inherits 'implementation'
}
// The STOMP broker lane. Its own source set for the same reason the Nginx one has its own: it
// is the only other lane that needs Docker, and folding it into `test` would make every
// developer's `check` depend on a container runtime.
sourceSet('brokerRelayTest') {
- compilesAgainst 'main', 'testkit'
+ compilesAgainst 'main', 'testFixtures'
inherits 'implementation'
}
}
-testkitPublisher {
- consumedBy 'test'
- publishAs 'websocketTestkit'
-}
-
dependencies {
- testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
+ // Declared rather than inherited: the `testkit` source set extended testImplementation, so the
+ // fixtures saw every test library this leaf declared without ever saying so (ADR-BUILD-001).
+ testFixturesImplementation 'org.assertj:assertj-core'
+ testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api'
+ testFixturesImplementation 'org.springframework.boot:spring-boot-test'
+ testFixturesImplementation 'org.springframework:spring-context'
+ testFixturesImplementation 'org.springframework:spring-web'
+ testFixturesImplementation 'org.springframework:spring-websocket'
+ testFixturesImplementation 'org.springframework.boot:spring-boot-autoconfigure'
+ testFixturesImplementation 'org.springframework.boot:spring-boot'
+ testFixturesImplementation libs.archunit.junit5
+ testImplementation libs.archunit.junit5
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.boot:spring-boot-starter-websocket'
diff --git a/src/adapter/inbound/websocket/gradle.lockfile b/src/adapter/inbound/websocket/gradle.lockfile
index 5c29e6ad..e283c5b7 100644
--- a/src/adapter/inbound/websocket/gradle.lockfile
+++ b/src/adapter/inbound/websocket/gradle.lockfile
@@ -2,74 +2,74 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
-ch.qos.logback:logback-classic:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-ch.qos.logback:logback-core:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.21=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml:classmate:1.7.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+ch.qos.logback:logback-classic:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+ch.qos.logback:logback-core:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-annotations:2.21=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml:classmate:1.7.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.github.docker-java:docker-java-api:3.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
com.github.docker-java:docker-java-transport-zerodep:3.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
com.github.docker-java:docker-java-transport:3.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
-com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
com.github.spotbugs:spotbugs:4.10.2=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
-com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto:auto-common:1.2.2=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto:auto-common:1.2.2=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs
com.google.code.gson:gson:2.13.2=spotbugs
-com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.38.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
com.google.errorprone:error_prone_annotations:2.41.0=spotbugs
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
-com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:failureaccess:1.0.3=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:guava:33.5.0-jre=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:failureaccess:1.0.3=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:guava:33.5.0-jre=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.guava:guava:33.6.0-jre=checkstyle
-com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.10.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
-com.tngtech.archunit:archunit-junit5-api:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-api:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
commons-codec:commons-codec:1.19.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
commons-io:commons-io:2.20.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
commons-io:commons-io:2.21.0=spotbugs
-commons-logging:commons-logging:1.3.6=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-logging:commons-logging:1.3.6=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
info.picocli:picocli:4.7.7=checkstyle
-io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.micrometer:micrometer-commons:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-observation:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor:reactor-core:3.8.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.micrometer:micrometer-commons:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-observation:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor:reactor-core:3.8.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.annotation:jakarta.annotation-api:3.0.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.annotation:jakarta.annotation-api:3.0.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.enterprise:jakarta.enterprise.lang-model:4.1.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.inject:jakarta.inject-api:2.0.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.interceptor:jakarta.interceptor-api:2.2.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.1.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.transaction:jakarta.transaction-api:2.0.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
-jakarta.validation:jakarta.validation-api:3.1.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.validation:jakarta.validation-api:3.1.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.websocket:jakarta.websocket-api:2.2.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.websocket:jakarta.websocket-client-api:2.2.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+javax.inject:javax.inject:1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
jaxen:jaxen:2.0.6=spotbugs
net.bytebuddy:byte-buddy-agent:1.17.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.bytebuddy:byte-buddy:1.17.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.bytebuddy:byte-buddy:1.17.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.java.dev.jna:jna:5.18.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
net.minidev:accessors-smart:2.6.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.minidev:json-smart:2.6.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
@@ -82,19 +82,19 @@ org.apache.commons:commons-text:1.15.0=spotbugs
org.apache.commons:commons-text:1.3=checkstyle
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
-org.apache.logging.log4j:log4j-api:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.logging.log4j:log4j-api:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.logging.log4j:log4j-core:2.25.5=spotbugs
-org.apache.logging.log4j:log4j-to-slf4j:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.logging.log4j:log4j-to-slf4j:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
-org.apache.tomcat.embed:tomcat-embed-core:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.tomcat.embed:tomcat-embed-el:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-core:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-el:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
-org.apiguardian:apiguardian-api:1.1.2=brokerRelayTestCompileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
-org.assertj:assertj-core:3.27.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apiguardian:apiguardian-api:1.1.2=brokerRelayTestCompileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath
+org.assertj:assertj-core:3.27.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.awaitility:awaitility:4.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
@@ -132,24 +132,24 @@ org.eclipse.jetty:jetty-session:12.1.12=jettyWebSocketTestCompileClasspath,jetty
org.eclipse.jetty:jetty-util:12.1.12=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.1.12=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
org.hamcrest:hamcrest:3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hibernate.validator:hibernate-validator:9.0.1.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.hibernate.validator:hibernate-validator:9.0.1.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
-org.jboss.logging:jboss-logging:3.6.3.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jboss.logging:jboss-logging:3.6.3.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.jetbrains:annotations:17.0.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
-org.jspecify:jspecify:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,checkstyle,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jspecify:jspecify:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,checkstyle,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.jupiter:junit-jupiter-api:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-commons:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-engine:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-commons:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-engine:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.junit:junit-bom:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit:junit-bom:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
org.mockito:mockito-core:5.20.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,mockitoAgent,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.objenesis:objenesis:3.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.opentest4j:opentest4j:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.opentest4j:opentest4j:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.osgi:org.osgi.annotation.bundle:2.0.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.annotation.versioning:1.1.2=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.resource:1.0.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
@@ -160,63 +160,63 @@ org.ow2.asm:asm-tree:9.10.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTes
org.ow2.asm:asm-util:9.10.1=spotbugs
org.ow2.asm:asm:9.10.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,spotbugs
org.ow2.asm:asm:9.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.pcollections:pcollections:4.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-org.reactivestreams:reactive-streams:1.0.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.pcollections:pcollections:4.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+org.reactivestreams:reactive-streams:1.0.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
org.rnorth.duct-tape:duct-tape:1.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
org.skyscreamer:jsonassert:1.5.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:jul-to-slf4j:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:slf4j-api:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:jul-to-slf4j:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:slf4j-api:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.18=checkstyle
-org.springframework.boot:spring-boot-autoconfigure:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-autoconfigure:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor
-org.springframework.boot:spring-boot-http-converter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-http-converter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-jetty:4.0.8=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
org.springframework.boot:spring-boot-resttestclient:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-servlet:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-servlet:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jetty-runtime:4.0.8=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-jetty:4.0.8=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath
-org.springframework.boot:spring-boot-starter-logging:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-logging:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-web-server:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-web-server:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-aop:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-beans:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-context:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-core:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-expression:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-messaging:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-test:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-web:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-webflux:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-webmvc:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-websocket:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-aop:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-beans:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-context:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-core:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-expression:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-messaging:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-test:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-web:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-webflux:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-webmvc:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-websocket:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.testcontainers:testcontainers-junit-jupiter:2.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
org.testcontainers:testcontainers-rabbitmq:2.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath
org.testcontainers:testcontainers:2.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
org.xmlunit:xmlunit-core:2.10.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.yaml:snakeyaml:2.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson.core:jackson-core:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson.core:jackson-databind:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.yaml:snakeyaml:2.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+tools.jackson.core:jackson-core:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+tools.jackson.core:jackson-databind:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson:jackson-bom:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+tools.jackson:jackson-bom:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
empty=
diff --git a/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java b/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java
index bba4fe39..03365882 100644
--- a/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java
+++ b/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java
@@ -7,6 +7,9 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@@ -43,7 +46,62 @@ class BinaryCodecBackendScopeTest {
}
}
+ /**
+ * Reports whether the build file declares the coordinate in the given scope.
+ *
+ *
Two notations mean the same declaration. The literal {@code 'group:artifact:version'} form
+ * is matched directly; the version-catalog form is matched by resolving the leaf's {@code libs.*}
+ * accessors back to their modules. Matching only the literal string would have read {@code
+ * compileOnly libs.protobuf.java} as "not declared" — a scope contract reported as broken because
+ * versions moved into a catalog, which is a false positive expensive enough to be worth the
+ * parser.
+ */
private static boolean declares(String scope, String coordinate) {
+ if (declaresLiterally(scope, coordinate)) {
+ return true;
+ }
+ for (Map.Entry alias : catalogModules().entrySet()) {
+ if (alias.getValue().equals(coordinate)
+ && Pattern.compile(
+ "^\\s*" + scope + "\\s+" + Pattern.quote(alias.getKey()) + "\\s*$",
+ Pattern.MULTILINE)
+ .matcher(buildFile())
+ .find()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Version-catalog accessor to {@code group:artifact}, parsed from the catalog itself. */
+ private static Map catalogModules() {
+ Path catalog =
+ WebSocketBuildModel.mainSourceRoot()
+ .getParent()
+ .getParent()
+ .getParent()
+ .getParent()
+ .getParent()
+ .getParent()
+ .resolve("gradle/libs.versions.toml");
+ Map modules = new LinkedHashMap<>();
+ if (!Files.isRegularFile(catalog)) {
+ return modules;
+ }
+ try {
+ Matcher entry =
+ Pattern.compile("(?m)^([A-Za-z0-9_-]+)\\s*=\\s*\\{[^}]*module\\s*=\\s*\"([^\"]+)\"")
+ .matcher(Files.readString(catalog));
+ while (entry.find()) {
+ modules.put("libs." + entry.group(1).replace('-', '.').replace('_', '.'), entry.group(2));
+ }
+ } catch (IOException failure) {
+ throw new UncheckedIOException(failure);
+ }
+ return modules;
+ }
+
+ private static boolean declaresLiterally(String scope, String coordinate) {
return Pattern.compile(
"^\\s*" + scope + "\\s+'" + Pattern.quote(coordinate) + "(:|')", Pattern.MULTILINE)
.matcher(buildFile())
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java
diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java
similarity index 100%
rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java
rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java
diff --git a/src/adapter/outbound/fileserver/build.gradle b/src/adapter/outbound/fileserver/build.gradle
index a34958ba..bec5be40 100644
--- a/src/adapter/outbound/fileserver/build.gradle
+++ b/src/adapter/outbound/fileserver/build.gradle
@@ -12,5 +12,11 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-autoconfigure'
implementation 'org.slf4j:slf4j-api'
+ // Micrometer core for the metric sink that implements FileserverMetricsPort. It used to sit in
+ // the composition root next to the auto-configuration that wires it; but the class implements an
+ // application port over an external system, which makes it an outbound adapter and this its leaf
+ // (BOOT-017). The composition root still decides whether to wire it — that part is assembly.
+ implementation 'io.micrometer:micrometer-core'
+
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}
diff --git a/src/adapter/outbound/fileserver/gradle.lockfile b/src/adapter/outbound/fileserver/gradle.lockfile
index 17910be7..602b0296 100644
--- a/src/adapter/outbound/fileserver/gradle.lockfile
+++ b/src/adapter/outbound/fileserver/gradle.lockfile
@@ -41,6 +41,7 @@ info.picocli:picocli:4.7.7=checkstyle
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor
io.micrometer:micrometer-commons:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-core:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-observation:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.4=testCompileClasspath,testRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:3.0.0=testCompileClasspath,testRuntimeClasspath
@@ -79,6 +80,7 @@ org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
org.dom4j:dom4j:2.2.0=spotbugs
org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath
+org.hdrhistogram:HdrHistogram:2.2.2=runtimeClasspath,testRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:6.0.3=testCompileClasspath,testRuntimeClasspath
@@ -90,6 +92,7 @@ org.junit.platform:junit-platform-engine:6.0.3=testRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=testRuntimeClasspath
org.junit:junit-bom:6.0.3=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
+org.latencyutils:LatencyUtils:2.0.3=runtimeClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.20.0=mockitoAgent,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testRuntimeClasspath
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/MicrometerFileserverMetrics.java b/src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability/MicrometerFileserverMetrics.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/MicrometerFileserverMetrics.java
rename to src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability/MicrometerFileserverMetrics.java
index 2f2fe7a4..1842d822 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/MicrometerFileserverMetrics.java
+++ b/src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability/MicrometerFileserverMetrics.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.autoconfigure.fileserver;
+package dev.caskeleton.adapter.outbound.fileserver.platform.observability;
import dev.caskeleton.application.fileserver.api.transfer.UploadProtocol;
import dev.caskeleton.application.fileserver.observability.FileserverMetricsPort;
diff --git a/src/adapter/outbound/httpclient/build.gradle b/src/adapter/outbound/httpclient/build.gradle
index 47e011d4..68653b2f 100644
--- a/src/adapter/outbound/httpclient/build.gradle
+++ b/src/adapter/outbound/httpclient/build.gradle
@@ -1,3 +1,6 @@
+// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
+apply plugin: 'java-test-fixtures'
+
// Outbound HTTP Client Platform leaf — see
// docs/superpowers/specs/2026-08-08-httpclient-platform-design.md (design package) and
// docs/httpclient/repository-adaptation.md (how the design's 19 library modules map here).
@@ -37,11 +40,11 @@ dependencies {
// Resilience4j supplies the execution primitives only. HTTP retry *eligibility* is owned by
// this module (design D-09) and never delegated to a generic retry library.
- implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
- implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
- implementation 'io.github.resilience4j:resilience4j-ratelimiter:2.2.0'
- implementation 'io.github.resilience4j:resilience4j-bulkhead:2.2.0'
- implementation 'io.github.resilience4j:resilience4j-micrometer:2.2.0'
+ implementation libs.resilience4j.retry
+ implementation libs.resilience4j.circuitbreaker
+ implementation libs.resilience4j.ratelimiter
+ implementation libs.resilience4j.bulkhead
+ implementation libs.resilience4j.micrometer
implementation 'org.springframework.security:spring-security-oauth2-client'
implementation 'com.fasterxml.jackson.core:jackson-databind'
@@ -61,14 +64,14 @@ dependencies {
// Testkit dependencies (design §28.1 test topology). They are test-scoped so no production
// module can depend on the testkit; the testkit source set inherits them by extending
// testImplementation, and the test lanes use them directly.
- testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
- testImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
+ testImplementation libs.mockwebserver
+ testImplementation libs.okhttp.tls
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
testImplementation 'org.testcontainers:testcontainers-toxiproxy'
testImplementation 'io.projectreactor:reactor-test'
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE'
+ testImplementation libs.archunit.junit5
+ testImplementation libs.blockhound
}
// The testkit is its own source set, not part of `test`, because three lanes consume it and only
@@ -84,32 +87,37 @@ dependencies {
// the default unit lane.
strictTestLanes {
// The testkit compiles against exactly what a test does: `implementation` inheritance runs
- // through testImplementation, so this is the module's own dependencies plus the test libraries.
- sourceSet('testkit') { compilesAgainst 'main' }
- sourceSet('httpClientPerformanceTest') { compilesAgainst 'main', 'testkit' }
- sourceSet('jmh') { compilesAgainst 'main', 'testkit' }
-}
-
-// Every test lane compiles and runs against the testkit.
-sourceSets.test {
- compileClasspath += sourceSets.testkit.output
- runtimeClasspath += sourceSets.testkit.output
+ sourceSet('httpClientPerformanceTest') { compilesAgainst 'main', 'testFixtures' }
+ sourceSet('jmh') { compilesAgainst 'main', 'testFixtures' }
}
dependencies {
// Testkit dependencies (design §28.1 test topology). They stay off the production
// configurations, so no production module can depend on the testkit.
- testkitImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
- testkitImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
- testkitImplementation 'org.testcontainers:testcontainers'
- testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter'
- testkitImplementation 'org.testcontainers:testcontainers-toxiproxy'
- testkitImplementation 'io.projectreactor:reactor-test'
- testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testkitImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE'
+ // Declared rather than inherited. The `testkit` source set extended `testImplementation`, so the
+ // fixtures compiled against every test library this leaf happened to declare; moving to
+ // testFixtures makes that surface explicit (ADR-BUILD-001). These six were invisible before.
+ testFixturesImplementation 'io.micrometer:micrometer-core'
+ testFixturesImplementation 'org.springframework:spring-web'
+ testFixturesImplementation 'io.projectreactor:reactor-core'
+ testFixturesImplementation 'org.assertj:assertj-core'
+ testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api'
+ testFixturesImplementation 'org.slf4j:slf4j-api'
+ testFixturesImplementation libs.toxiproxy.java
+ // The Netty leak detector reads logback's own event types to catch a LEAK record.
+ testFixturesImplementation 'io.netty:netty-common'
+ testFixturesImplementation 'ch.qos.logback:logback-classic'
+ testFixturesImplementation libs.mockwebserver
+ testFixturesImplementation libs.okhttp.tls
+ testFixturesImplementation 'org.testcontainers:testcontainers'
+ testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter'
+ testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy'
+ testFixturesImplementation 'io.projectreactor:reactor-test'
+ testFixturesImplementation libs.archunit.junit5
+ testFixturesImplementation libs.blockhound
- jmhImplementation 'org.openjdk.jmh:jmh-core:1.37'
- jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
+ jmhImplementation libs.jmh.core
+ jmhAnnotationProcessor libs.jmh.generator.annprocess
}
// UTF-8 is pinned for every JavaCompile task in the root build; this leaf no longer repeats it.
@@ -176,6 +184,17 @@ strictTestLanes {
description = 'Runs the cross-transport stable contract suite (design §28.2, §33).'
customize = { test -> applyContractSelection(test) }
}
+ // The same contract suite, run under the name the release gate and the support matrix quote for
+ // the repository's Spring 7 baseline. It used to be a hand-written `tasks.register(..., Test)`
+ // that copied the stable lane and dropped `failOnNoDiscoveredTests` — the sixth instance of the
+ // copy-paste failure the comment above records, and the one that mattered most, since three
+ // workflows call it by name as a release gate. Declared here so the convention supplies both
+ // fail-closed discovery and the refusal to report success on a run that executed nothing.
+ lane('spring70CompatibilityTest') {
+ tag = 'httpclient-contract'
+ description = 'Runs the contract suite on the repository Spring 7 baseline (design §29).'
+ customize = { test -> applyContractSelection(test) }
+ }
lane('httpClientSecurityTest') {
tag = 'httpclient-security'
description = 'Runs the SSRF, credential-leak, and cardinality suite (design §28.5, §28.7).'
@@ -246,13 +265,3 @@ tasks.named('check') {
'httpClientBlockHoundTest',
'spring62ApiSurfaceScan'
}
-
-tasks.register('spring70CompatibilityTest', Test) {
- group = 'verification'
- description = 'Runs the contract suite on the repository Spring 7 baseline (design §29).'
- testClassesDirs = sourceSets.test.output.classesDirs
- classpath = sourceSets.test.runtimeClasspath
- useJUnitPlatform { includeTags 'httpclient-contract' }
- applyContractSelection(it)
- outputs.upToDateWhen { false }
-}
diff --git a/src/adapter/outbound/httpclient/gradle.lockfile b/src/adapter/outbound/httpclient/gradle.lockfile
index 5f186c01..be0ad990 100644
--- a/src/adapter/outbound/httpclient/gradle.lockfile
+++ b/src/adapter/outbound/httpclient/gradle.lockfile
@@ -2,125 +2,126 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath
-ch.qos.logback:logback-classic:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-ch.qos.logback:logback-core:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.github.docker-java:docker-java-api:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.docker-java:docker-java-transport-zerodep:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.docker-java:docker-java-transport:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+ch.qos.logback:logback-classic:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+ch.qos.logback:logback-core:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.github.docker-java:docker-java-api:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.docker-java:docker-java-transport-zerodep:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.docker-java:docker-java-transport:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
com.github.spotbugs:spotbugs:4.10.2=spotbugs
-com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto:auto-common:1.2.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto:auto-common:1.2.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs
-com.google.code.gson:gson:2.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_annotations:2.41.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.code.gson:gson:2.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.41.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
-com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:guava:33.5.0-jre=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:guava:33.5.0-jre=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.guava:guava:33.6.0-jre=checkstyle
-com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.10.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.nimbusds:content-type:2.3=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.nimbusds:lang-tag:1.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.nimbusds:content-type:2.3=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.nimbusds:lang-tag:1.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
-com.squareup.okhttp3:mockwebserver:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.squareup.okhttp3:okhttp-tls:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.squareup.okhttp3:okhttp:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.squareup.okio:okio-jvm:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.squareup.okio:okio:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-api:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.squareup.okhttp3:mockwebserver:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.squareup.okhttp3:okhttp-tls:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.squareup.okhttp3:okhttp:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.squareup.okio:okio-jvm:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.squareup.okio:okio:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-api:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
-commons-codec:commons-codec:1.19.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-codec:commons-codec:1.19.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
-commons-io:commons-io:2.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-io:commons-io:2.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-io:commons-io:2.21.0=spotbugs
-commons-logging:commons-logging:1.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-logging:commons-logging:1.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+eu.rekawek.toxiproxy:toxiproxy-java:2.1.7=testFixturesCompileClasspath,testFixturesRuntimeClasspath
info.picocli:picocli:4.7.7=checkstyle
-io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.github.resilience4j:resilience4j-bulkhead:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.github.resilience4j:resilience4j-core:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.github.resilience4j:resilience4j-micrometer:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.github.resilience4j:resilience4j-ratelimiter:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.github.resilience4j:resilience4j-retry:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.github.resilience4j:resilience4j-timelimiter:2.2.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-commons:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-core:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-observation:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-buffer:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-base:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-classes-quic:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-compression:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-http2:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-http3:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-http:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-native-quic:4.2.17.Final=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-io.netty:netty-codec-socks:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-handler-proxy:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-handler:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-resolver-dns-native-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-resolver-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-resolver:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-transport-classes-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-transport-native-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-transport-native-unix-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.netty:netty-transport:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor.netty:reactor-netty-core:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor.netty:reactor-netty-http:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor.tools:blockhound:1.0.17.RELEASE=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor:reactor-core:3.8.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor:reactor-test:3.8.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.github.resilience4j:resilience4j-bulkhead:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.resilience4j:resilience4j-core:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.resilience4j:resilience4j-micrometer:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.resilience4j:resilience4j-ratelimiter:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.resilience4j:resilience4j-retry:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.resilience4j:resilience4j-timelimiter:2.2.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-commons:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-core:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-observation:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-buffer:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-base:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-classes-quic:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-compression:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-http2:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-http3:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-http:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-native-quic:4.2.17.Final=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+io.netty:netty-codec-socks:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-handler-proxy:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-handler:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-resolver-dns-native-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-resolver-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-resolver:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-transport-classes-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-transport-native-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-transport-native-unix-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.netty:netty-transport:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor.netty:reactor-netty-core:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor.netty:reactor-netty-http:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor.tools:blockhound:1.0.17.RELEASE=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor:reactor-core:3.8.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor:reactor-test:3.8.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.4=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:3.0.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+javax.inject:javax.inject:1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
jaxen:jaxen:2.0.6=spotbugs
-junit:junit:4.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+junit:junit:4.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.17.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.bytebuddy:byte-buddy:1.17.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.java.dev.jna:jna:5.18.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.minidev:accessors-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.minidev:json-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.bytebuddy:byte-buddy:1.17.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.java.dev.jna:jna:5.18.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.minidev:accessors-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.minidev:json-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.sf.jopt-simple:jopt-simple:5.0.4=jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
org.antlr:antlr4-runtime:4.13.2=checkstyle
org.apache.bcel:bcel:6.12.0=spotbugs
-org.apache.commons:commons-compress:1.28.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.commons:commons-lang3:3.20.0=checkstyle,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-compress:1.28.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-lang3:3.20.0=checkstyle,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.commons:commons-math3:3.6.1=jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath
org.apache.commons:commons-text:1.15.0=spotbugs
org.apache.commons:commons-text:1.3=checkstyle
-org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
org.apache.logging.log4j:log4j-api:2.25.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
@@ -134,16 +135,16 @@ org.apache.tomcat.embed:tomcat-embed-core:11.0.24=httpClientPerformanceTestCompi
org.apache.tomcat.embed:tomcat-embed-el:11.0.24=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
-org.apiguardian:apiguardian-api:1.1.2=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath
-org.assertj:assertj-core:3.27.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apiguardian:apiguardian-api:1.1.2=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath
+org.assertj:assertj-core:3.27.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.awaitility:awaitility:4.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
org.dom4j:dom4j:2.2.0=spotbugs
-org.eclipse.jetty.compression:jetty-compression-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.eclipse.jetty.compression:jetty-compression-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.eclipse.jetty.http3:jetty-http3-client-transport:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.eclipse.jetty.http3:jetty-http3-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.eclipse.jetty.http3:jetty-http3-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
@@ -152,37 +153,37 @@ org.eclipse.jetty.quic:jetty-quic-api:12.1.12=compileClasspath,httpClientPerform
org.eclipse.jetty.quic:jetty-quic-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.eclipse.jetty.quic:jetty-quic-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.eclipse.jetty.quic:jetty-quic-util:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.eclipse.jetty:jetty-alpn-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.eclipse.jetty:jetty-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.eclipse.jetty:jetty-http:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.eclipse.jetty:jetty-io:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.eclipse.jetty:jetty-util:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hamcrest:hamcrest-core:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hamcrest:hamcrest:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hdrhistogram:HdrHistogram:2.2.2=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.eclipse.jetty:jetty-alpn-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.eclipse.jetty:jetty-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.eclipse.jetty:jetty-http:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.eclipse.jetty:jetty-io:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.eclipse.jetty:jetty-util:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.hamcrest:hamcrest-core:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.hamcrest:hamcrest:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.hdrhistogram:HdrHistogram:2.2.2=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
-org.jetbrains.kotlin:kotlin-stdlib-common:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jetbrains.kotlin:kotlin-stdlib:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jetbrains:annotations:17.0.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,httpClientPerformanceTestAnnotationProcessor,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jetbrains.kotlin:kotlin-stdlib-common:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jetbrains.kotlin:kotlin-stdlib:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jetbrains:annotations:17.0.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,httpClientPerformanceTestAnnotationProcessor,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.jupiter:junit-jupiter-api:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-commons:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-engine:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-commons:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-engine:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.junit:junit-bom:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit:junit-bom:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
-org.latencyutils:LatencyUtils:2.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.latencyutils:LatencyUtils:2.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.mockito:mockito-core:5.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,mockitoAgent,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.objenesis:objenesis:3.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.openjdk.jmh:jmh-core:1.37=jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath
org.openjdk.jmh:jmh-generator-annprocess:1.37=jmhAnnotationProcessor
-org.opentest4j:opentest4j:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.opentest4j:opentest4j:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.osgi:org.osgi.annotation.bundle:2.0.0=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.annotation.versioning:1.1.2=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.resource:1.0.0=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath
@@ -192,17 +193,17 @@ org.ow2.asm:asm-commons:9.10.1=spotbugs
org.ow2.asm:asm-tree:9.10.1=spotbugs
org.ow2.asm:asm-util:9.10.1=spotbugs
org.ow2.asm:asm:9.10.1=spotbugs
-org.ow2.asm:asm:9.7.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.pcollections:pcollections:4.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-org.reactivestreams:reactive-streams:1.0.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.ow2.asm:asm:9.7.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.pcollections:pcollections:4.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+org.reactivestreams:reactive-streams:1.0.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
-org.rnorth.duct-tape:duct-tape:1.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.rnorth.duct-tape:duct-tape:1.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.skyscreamer:jsonassert:1.5.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.slf4j:jul-to-slf4j:2.0.18=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:slf4j-api:2.0.18=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:slf4j-api:2.0.18=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.18=checkstyle
-org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-http-converter:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-jackson:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-resttestclient:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
@@ -222,28 +223,28 @@ org.springframework.boot:spring-boot-tomcat:4.0.8=httpClientPerformanceTestCompi
org.springframework.boot:spring-boot-web-server:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc-test:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.security:spring-security-web:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-aop:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-beans:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-context:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-core:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-expression:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.security:spring-security-web:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-aop:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-beans:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-context:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-core:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-expression:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-test:7.0.9=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-web:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-webflux:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-web:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-webflux:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-webmvc:7.0.9=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-junit-jupiter:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-toxiproxy:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-junit-jupiter:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-toxiproxy:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
org.xmlunit:xmlunit-core:2.10.4=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.yaml:snakeyaml:2.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson.core:jackson-core:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson.core:jackson-databind:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-tools.jackson:jackson-bom:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+tools.jackson.core:jackson-core:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+tools.jackson.core:jackson-databind:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+tools.jackson:jackson-bom:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
empty=
diff --git a/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java b/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java
index 76e10b27..204f9bf3 100644
--- a/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java
+++ b/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java
@@ -11,20 +11,27 @@ import com.tngtech.archunit.core.importer.ImportOption;
* rule classes silently disagreeing about it is how a boundary stops being enforced.
*
*
ArchUnit's {@code DO_NOT_INCLUDE_TESTS} recognises the conventional test output locations, and
- * the testkit is no longer in one: it is its own source set, so it compiles to {@code
- * build/classes/java/testkit}. Left in, the fixtures would be imported as production code and every
+ * the fixtures are not in one: they are their own source set, compiling to {@code
+ * build/classes/java/testFixtures}. Left in, they would be imported as production code and every
* rule here would be asserted against them — starting with the one that says production code never
- * depends on the testkit, which the testkit itself trivially does.
+ * depends on the fixtures, which the fixtures themselves trivially do.
+ *
+ *
The path moved when this leaf adopted {@code java-test-fixtures} (ADR-BUILD-001) and the rule
+ * caught it: the exclusion still named {@code /classes/java/testkit/}, so the fixtures were suddenly
+ * production and the boundary test failed on the first run. That is the check working — an import
+ * filter that silently stops matching is a rule asserted against the wrong corpus.
*/
public final class PlatformClasses {
- private static final ImportOption NOT_THE_TESTKIT_SOURCE_SET =
- location -> !location.contains("/classes/java/testkit/");
+ private static final ImportOption NOT_THE_FIXTURES_SOURCE_SET =
+ location ->
+ !location.contains("/classes/java/testFixtures/")
+ && !location.contains("test-fixtures");
private static final JavaClasses PRODUCTION =
new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
- .withImportOption(NOT_THE_TESTKIT_SOURCE_SET)
+ .withImportOption(NOT_THE_FIXTURES_SOURCE_SET)
.importPackages("dev.caskeleton.adapter.outbound.httpclient");
private PlatformClasses() {}
@@ -32,7 +39,7 @@ public final class PlatformClasses {
/**
* Returns the platform's production classes.
*
- * @return every compiled class of this module that is neither a test nor a testkit fixture
+ * @return every compiled class of this module that is neither a test nor a test fixture
*/
public static JavaClasses production() {
return PRODUCTION;
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java
diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java
similarity index 100%
rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java
rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java
diff --git a/src/adapter/outbound/identifier/build.gradle b/src/adapter/outbound/identifier/build.gradle
index eed16a2c..3f4c92e8 100644
--- a/src/adapter/outbound/identifier/build.gradle
+++ b/src/adapter/outbound/identifier/build.gradle
@@ -6,7 +6,7 @@ plugins {
dependencies {
implementation project(':application-core')
- testImplementation 'org.spockframework:spock-core:2.4-groovy-5.0'
+ testImplementation libs.spock.core.groovy5
}
// Pin UTF-8 so non-ASCII (Korean) Spock spec names build on any host. See README.
diff --git a/src/adapter/outbound/messaging/build.gradle b/src/adapter/outbound/messaging/build.gradle
index d23001ca..c7313587 100644
--- a/src/adapter/outbound/messaging/build.gradle
+++ b/src/adapter/outbound/messaging/build.gradle
@@ -6,7 +6,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-autoconfigure'
implementation 'org.springframework.boot:spring-boot-starter-json'
- implementation('com.networknt:json-schema-validator:3.0.2') {
+ implementation(libs.json.schema.validator) {
exclude group: 'com.fasterxml.jackson.dataformat', module: 'jackson-dataformat-yaml'
}
implementation 'org.slf4j:slf4j-api'
@@ -48,9 +48,9 @@ tasks.register('verifyJsonSchemaRuntimeGraph') {
"Messaging JSON runtime contains forbidden Jackson 2/YAML modules: ${forbidden}")
}
[
- 'com.networknt:json-schema-validator:3.0.2',
- 'tools.jackson.core:jackson-core:3.0.2',
- 'tools.jackson.core:jackson-databind:3.0.2'
+ libs.json.schema.validator,
+ libs.jackson3.core,
+ libs.jackson3.databind
].each { String required ->
if (!modules.contains(required)) {
throw new GradleException(
diff --git a/src/adapter/outbound/notification/build.gradle b/src/adapter/outbound/notification/build.gradle
index 517a9501..06b95495 100644
--- a/src/adapter/outbound/notification/build.gradle
+++ b/src/adapter/outbound/notification/build.gradle
@@ -7,6 +7,13 @@ dependencies {
implementation 'org.springframework:spring-web' // Slack webhook client (RestClient)
implementation 'org.slf4j:slf4j-api'
+ // Micrometer core for the metric sink that implements NotificationMetricsPort. The sink used
+ // to live in the composition root because the MeterRegistry does; but a MeterRegistry is an
+ // external system like any other, and an implementation of an application port is an
+ // outbound adapter wherever its dependency happens to be configured (BOOT-017). Its logging
+ // counterpart, LoggingNotificationMetrics, was already here.
+ implementation 'io.micrometer:micrometer-core'
+
// Notification Delivery Platform.
// - mail: the SMTP provider adapter is built on JavaMailSender/MimeMessageHelper, which is where
// multipart/alternative, inline resources and header validation already live. Rebuilding MIME
@@ -32,7 +39,7 @@ dependencies {
// adapter that renders strings and never serves a request.
implementation 'org.thymeleaf:thymeleaf'
- implementation('com.networknt:json-schema-validator:3.0.2') {
+ implementation(libs.json.schema.validator) {
exclude group: 'tools.jackson.dataformat', module: 'jackson-dataformat-yaml'
exclude group: 'com.fasterxml.jackson.dataformat', module: 'jackson-dataformat-yaml'
}
diff --git a/src/adapter/outbound/notification/gradle.lockfile b/src/adapter/outbound/notification/gradle.lockfile
index b30c50f9..b7a0b5fe 100644
--- a/src/adapter/outbound/notification/gradle.lockfile
+++ b/src/adapter/outbound/notification/gradle.lockfile
@@ -43,6 +43,7 @@ info.picocli:picocli:4.7.7=checkstyle
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor
io.micrometer:micrometer-commons:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-core:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-observation:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.8.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.projectreactor:reactor-test:3.8.7=testCompileClasspath,testRuntimeClasspath
@@ -88,6 +89,7 @@ org.dom4j:dom4j:2.2.0=spotbugs
org.eclipse.angus:angus-activation:2.0.3=runtimeClasspath,testRuntimeClasspath
org.eclipse.angus:angus-mail:2.0.5=runtimeClasspath,testRuntimeClasspath
org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath
+org.hdrhistogram:HdrHistogram:2.2.2=runtimeClasspath,testRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
org.javassist:javassist:3.29.0-GA=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
@@ -100,6 +102,7 @@ org.junit.platform:junit-platform-engine:6.0.3=testRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=testRuntimeClasspath
org.junit:junit-bom:6.0.3=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
+org.latencyutils:LatencyUtils:2.0.3=runtimeClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.20.0=mockitoAgent,testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=testCompileClasspath,testRuntimeClasspath
org.objenesis:objenesis:3.3=testRuntimeClasspath
diff --git a/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java
new file mode 100644
index 00000000..18e586dc
--- /dev/null
+++ b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java
@@ -0,0 +1,62 @@
+package dev.caskeleton.adapter.outbound.notification.platform.callback;
+
+import dev.caskeleton.application.notification.platform.api.ProviderProfileId;
+import dev.caskeleton.application.notification.platform.callback.CallbackLimits;
+import dev.caskeleton.application.notification.platform.callback.ProviderCallbackAdapter;
+import dev.caskeleton.application.notification.platform.callback.ProviderCallbackAdapterRegistry;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Profile-to-adapter lookup over a map fixed at assembly time.
+ *
+ *
This used to be an anonymous class inside the composition root's registries configuration
+ * (BOOT-017). Two things were wrong with that, and only one of them was placement.
+ *
+ *
The placement: an implementation of an {@code application-core} port is an outbound adapter,
+ * and building it from a map the composition root assembled does not change what it is. The
+ * composition root still decides the map's contents; what it no longer holds is the lookup, the
+ * refusal and the limits.
+ *
+ *
The other, which the move surfaced: {@link #require} runs per callback request, not
+ * at startup. While it lived in bootstrap it was briefly migrated to the startup-failure factory
+ * along with that leaf's genuine startup guards, which would have classified an unknown profile in
+ * an inbound request as a configuration error carrying a JVM exit code. Here the contract is the
+ * one the interface documents and every sibling {@code require(...)} registry in this repository
+ * uses.
+ *
+ *
Refusing an unknown profile is a security property rather than a convenience: an endpoint that
+ * accepted any profile name would let a caller choose which signature check runs.
+ */
+public final class MapProviderCallbackAdapterRegistry implements ProviderCallbackAdapterRegistry {
+
+ private final Map byProfile;
+ private final CallbackLimits limits;
+
+ /**
+ * Creates the registry.
+ *
+ * @param byProfile the profiles this deployment actually configured, copied defensively
+ * @param limits the limits every callback endpoint of this platform enforces
+ */
+ public MapProviderCallbackAdapterRegistry(
+ Map byProfile, CallbackLimits limits) {
+ this.byProfile = Map.copyOf(Objects.requireNonNull(byProfile, "byProfile"));
+ this.limits = Objects.requireNonNull(limits, "limits");
+ }
+
+ @Override
+ public ProviderCallbackAdapter require(ProviderProfileId profileId) {
+ Objects.requireNonNull(profileId, "profileId");
+ ProviderCallbackAdapter adapter = byProfile.get(profileId);
+ if (adapter == null) {
+ throw new IllegalStateException("no callback adapter registered for the profile");
+ }
+ return adapter;
+ }
+
+ @Override
+ public CallbackLimits limitsFor(ProviderProfileId profileId) {
+ return limits;
+ }
+}
diff --git a/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java
new file mode 100644
index 00000000..09be9bb4
--- /dev/null
+++ b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java
@@ -0,0 +1,37 @@
+package dev.caskeleton.adapter.outbound.notification.platform.callback;
+
+import dev.caskeleton.application.notification.platform.api.ProviderId;
+import dev.caskeleton.application.notification.platform.callback.ProviderEventProjector;
+import dev.caskeleton.application.notification.platform.callback.ProviderEventProjectorRegistry;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * Provider-to-projector lookup over a map fixed at assembly time.
+ *
+ *
Moved out of the composition root alongside {@link MapProviderCallbackAdapterRegistry}
+ * (BOOT-017). It was a lambda there rather than an anonymous class, which is why the ArchUnit rule
+ * that caught its sibling could not see it — a lambda implementing an interface leaves no class for
+ * the rule to inspect. Leaving it behind would have meant the boundary held only where a tool
+ * happened to be able to check it, which is a worse state than not having the rule: the code would
+ * look compliant and be inconsistent.
+ */
+public final class MapProviderEventProjectorRegistry implements ProviderEventProjectorRegistry {
+
+ private final Map byProvider;
+
+ /**
+ * Creates the registry.
+ *
+ * @param byProvider the projectors this deployment declared, copied defensively
+ */
+ public MapProviderEventProjectorRegistry(Map byProvider) {
+ this.byProvider = Map.copyOf(Objects.requireNonNull(byProvider, "byProvider"));
+ }
+
+ @Override
+ public Optional find(ProviderId providerId) {
+ return Optional.ofNullable(byProvider.get(providerId));
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/MicrometerNotificationMetrics.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java
rename to src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/MicrometerNotificationMetrics.java
index 0d906d14..2a179515 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java
+++ b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/MicrometerNotificationMetrics.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification.observation;
+package dev.caskeleton.adapter.outbound.notification.platform.observation;
import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
import dev.caskeleton.application.notification.platform.observation.NotificationMetricsPort;
diff --git a/src/adapter/outbound/objectstorage/build.gradle b/src/adapter/outbound/objectstorage/build.gradle
index ad99e57d..6066003a 100644
--- a/src/adapter/outbound/objectstorage/build.gradle
+++ b/src/adapter/outbound/objectstorage/build.gradle
@@ -38,8 +38,8 @@ dependencies {
testImplementation 'org.testcontainers:testcontainers'
testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
testImplementation 'org.testcontainers:testcontainers-toxiproxy'
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testImplementation 'net.jqwik:jqwik:1.9.1'
+ testImplementation libs.archunit.junit5
+ testImplementation libs.jqwik
}
def objectStorageReadinessRegistry = rootProject.projectDir.parentFile.toPath()
diff --git a/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java b/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java
index 9bb2f579..07516605 100644
--- a/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java
+++ b/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java
@@ -9,6 +9,7 @@ import dev.caskeleton.adapter.outbound.objectstorage.provider.ObjectStorageProvi
import dev.caskeleton.application.objectstorage.identity.ObjectDestinationId;
import dev.caskeleton.application.objectstorage.model.ObjectCapabilityRequirement;
import dev.caskeleton.application.objectstorage.model.ObjectPublicationRequirement;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.nio.file.Path;
import java.time.Clock;
import java.util.LinkedHashMap;
@@ -234,10 +235,17 @@ public final class ObjectStorageBindingCompiler {
}
}
+ /**
+ * Reports whether this adapter is compiling bindings for a production deployment.
+ *
+ *
Delegates to the repository-wide profile vocabulary rather than matching literals here. This
+ * method used to accept {@code production} alongside {@code prod}, which no deployable artifact
+ * could ever present — the composition root requires exactly one of {@code local}, {@code dev},
+ * {@code prod} and refuses anything else — so the extra spelling only ever diverged from the six
+ * other callers of the same question in slice tests (BOOT-002).
+ */
private boolean productionProfileActive() {
- return activeProfiles.stream()
- .map(profile -> profile.toLowerCase(Locale.ROOT))
- .anyMatch(profile -> profile.equals("prod") || profile.equals("production"));
+ return RuntimeEnvironment.isProductionActive(activeProfiles);
}
private static String canonicalId(String label, String value) {
diff --git a/src/adapter/outbound/persistence-jpa/build.gradle b/src/adapter/outbound/persistence-jpa/build.gradle
index d57ec352..6f103539 100644
--- a/src/adapter/outbound/persistence-jpa/build.gradle
+++ b/src/adapter/outbound/persistence-jpa/build.gradle
@@ -1,3 +1,6 @@
+// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
+apply plugin: 'java-test-fixtures'
+
// 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 /
@@ -14,23 +17,13 @@
// the design's "no production module depends on the testkit" guarantee without a new Gradle project.
strictTestLanes {
sourceSet('postgresqlIntegrationTest') {
- compilesAgainst 'main'
+ // '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('testkit') { compilesAgainst 'main' }
- sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testkit' }
-}
-
-// The testkit as a consumable artifact.
-//
-// The rule pack in `testkit` was only ever exercised by its own fixture tests: nothing imported the
-// production graph and ran the rules against it, so "controller must not expose an entity" and
-// "domain must not depend on Hibernate" were verified as library code and applied to nothing. The
-// composition root is the only place that can see every runtime leaf at once, so it is where the
-// production suite belongs — and it needs the rules.
-testkitPublisher {
- consumedBy 'test', 'postgresqlIntegrationTest'
- publishAs 'jpaTestkit'
+ sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testFixtures' }
}
ext.jpaPostgreSqlEvidenceImage = 'postgres:16-alpine'
@@ -71,19 +64,31 @@ dependencies {
testImplementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'
testImplementation 'org.hibernate.orm:hibernate-envers'
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
+ testImplementation libs.archunit.junit5
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-postgresql'
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter'
postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-toxiproxy'
postgresqlIntegrationTestRuntimeOnly 'org.postgresql:postgresql'
- testkitImplementation 'org.testcontainers:testcontainers'
- testkitImplementation 'org.testcontainers:testcontainers-postgresql'
- testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter'
- testkitImplementation 'org.testcontainers:testcontainers-toxiproxy'
- testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testkitRuntimeOnly '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
@@ -354,3 +359,4 @@ apiSurface {
}
apply from: rootProject.file('gradle/jpa-evidence.gradle')
+
diff --git a/src/adapter/outbound/persistence-jpa/gradle.lockfile b/src/adapter/outbound/persistence-jpa/gradle.lockfile
index 8a34d2fd..e55a23e8 100644
--- a/src/adapter/outbound/persistence-jpa/gradle.lockfile
+++ b/src/adapter/outbound/persistence-jpa/gradle.lockfile
@@ -2,95 +2,96 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
-ch.qos.logback:logback-classic:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-ch.qos.logback:logback-core:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml:classmate:1.7.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.github.docker-java:docker-java-api:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.docker-java:docker-java-transport-zerodep:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.docker-java:docker-java-transport:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+ch.qos.logback:logback-classic:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+ch.qos.logback:logback-core:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml:classmate:1.7.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.github.docker-java:docker-java-api:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.docker-java:docker-java-transport-zerodep:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.docker-java:docker-java-transport:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
com.github.spotbugs:spotbugs:4.10.2=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
-com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto:auto-common:1.2.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto:auto-common:1.2.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs
-com.google.code.gson:gson:2.13.2=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testkitCompileClasspath,testkitRuntimeClasspath
-com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.code.gson:gson:2.13.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,testCompileClasspath
-com.google.errorprone:error_prone_annotations:2.41.0=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.errorprone:error_prone_annotations:2.41.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
-com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:guava:33.5.0-jre=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:guava:33.5.0-jre=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.guava:guava:33.6.0-jre=checkstyle
-com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.h2database:h2:2.4.240=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.h2database:h2:2.4.240=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.10.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.mysema.commons:mysema-commons-lang:0.2.4=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
com.querydsl:querydsl-core:5.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.querydsl:querydsl-jpa:5.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.sun.istack:istack-commons-runtime:4.1.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-api:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.sun.istack:istack-commons-runtime:4.1.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-api:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.zaxxer:HikariCP:7.0.2=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.zaxxer:HikariCP:7.0.2=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
-commons-codec:commons-codec:1.19.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-codec:commons-codec:1.19.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
-commons-io:commons-io:2.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-io:commons-io:2.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-io:commons-io:2.21.0=spotbugs
-commons-logging:commons-logging:1.3.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-logging:commons-logging:1.3.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+eu.rekawek.toxiproxy:toxiproxy-java:2.1.7=testFixturesCompileClasspath,testFixturesRuntimeClasspath
info.picocli:picocli:4.7.7=checkstyle
-io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.micrometer:micrometer-commons:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-core:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-observation:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor:reactor-core:3.8.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.smallrye:jandex:3.3.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-jakarta.activation:jakarta.activation-api:2.1.4=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.inject:jakarta.inject-api:2.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-jakarta.persistence:jakarta.persistence-api:3.2.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.micrometer:micrometer-commons:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-core:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-observation:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor:reactor-core:3.8.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.smallrye:jandex:3.3.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+jakarta.activation:jakarta.activation-api:2.1.4=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.inject:jakarta.inject-api:2.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+jakarta.persistence:jakarta.persistence-api:3.2.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+javax.inject:javax.inject:1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
jaxen:jaxen:2.0.6=spotbugs
net.bytebuddy:byte-buddy-agent:1.17.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.bytebuddy:byte-buddy:1.17.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.java.dev.jna:jna:5.18.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.bytebuddy:byte-buddy:1.17.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.java.dev.jna:jna:5.18.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.minidev:accessors-smart:2.6.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.minidev:json-smart:2.6.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
-org.antlr:antlr4-runtime:4.13.2=checkstyle,compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.antlr:antlr4-runtime:4.13.2=checkstyle,compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.bcel:bcel:6.12.0=spotbugs
-org.apache.commons:commons-compress:1.28.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.commons:commons-lang3:3.20.0=checkstyle,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-compress:1.28.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-lang3:3.20.0=checkstyle,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.commons:commons-text:1.15.0=spotbugs
org.apache.commons:commons-text:1.3=checkstyle
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
-org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.logging.log4j:log4j-core:2.25.5=spotbugs
-org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
@@ -99,45 +100,45 @@ org.apache.tomcat.embed:tomcat-embed-core:11.0.24=jpaPlatformPerformanceTestComp
org.apache.tomcat.embed:tomcat-embed-el:11.0.24=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
-org.apiguardian:apiguardian-api:1.1.2=jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
-org.aspectj:aspectjweaver:1.9.25.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apiguardian:apiguardian-api:1.1.2=jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath
+org.aspectj:aspectjweaver:1.9.25.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.assertj:assertj-core:3.27.7=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.awaitility:awaitility:4.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.checkerframework:checker-qual:3.55.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.checkerframework:checker-qual:3.55.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
org.dom4j:dom4j:2.2.0=spotbugs
-org.eclipse.angus:angus-activation:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.flywaydb:flyway-core:11.14.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.flywaydb:flyway-database-postgresql:11.14.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.glassfish.jaxb:jaxb-core:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.glassfish.jaxb:jaxb-runtime:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.glassfish.jaxb:txw2:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.eclipse.angus:angus-activation:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.flywaydb:flyway-core:11.14.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.flywaydb:flyway-database-postgresql:11.14.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.glassfish.jaxb:jaxb-core:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.glassfish.jaxb:jaxb-runtime:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.glassfish.jaxb:txw2:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.hamcrest:hamcrest:3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hdrhistogram:HdrHistogram:2.2.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.hibernate.models:hibernate-models:1.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.hibernate.orm:hibernate-core:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hibernate.orm:hibernate-envers:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.hdrhistogram:HdrHistogram:2.2.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.hibernate.models:hibernate-models:1.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.hibernate.orm:hibernate-core:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.hibernate.orm:hibernate-envers:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
-org.jboss.logging:jboss-logging:3.6.3.Final=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.jetbrains:annotations:17.0.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jpaPlatformPerformanceTestAnnotationProcessor,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestAnnotationProcessor,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jboss.logging:jboss-logging:3.6.3.Final=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.jetbrains:annotations:17.0.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jpaPlatformPerformanceTestAnnotationProcessor,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestAnnotationProcessor,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.jupiter:junit-jupiter-api:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-commons:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-engine:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-commons:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-engine:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.junit:junit-bom:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit:junit-bom:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
-org.latencyutils:LatencyUtils:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.latencyutils:LatencyUtils:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.mockito:mockito-core:5.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,mockitoAgent,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.objenesis:objenesis:3.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.opentest4j:opentest4j:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.opentest4j:opentest4j:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.resource:1.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
@@ -148,76 +149,76 @@ org.ow2.asm:asm-tree:9.10.1=spotbugs
org.ow2.asm:asm-util:9.10.1=spotbugs
org.ow2.asm:asm:9.10.1=spotbugs
org.ow2.asm:asm:9.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.pcollections:pcollections:4.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-org.postgresql:postgresql:42.7.13=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.pcollections:pcollections:4.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+org.postgresql:postgresql:42.7.13=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
-org.rnorth.duct-tape:duct-tape:1.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.rnorth.duct-tape:duct-tape:1.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.skyscreamer:jsonassert:1.5.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:slf4j-api:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:slf4j-api:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.18=checkstyle
-org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor
-org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-hibernate:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-hibernate:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-http-converter:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-jackson:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-resttestclient:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-servlet:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jackson:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-tomcat:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-tomcat:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-web-server:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.data:spring-data-commons:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.data:spring-data-jpa:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.integration:spring-integration-core:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.integration:spring-integration-jdbc:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-aop:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-aspects:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-beans:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-context:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-core:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-expression:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-jdbc:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-messaging:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-orm:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.data:spring-data-commons:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.data:spring-data-jpa:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.integration:spring-integration-core:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.integration:spring-integration-jdbc:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-aop:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-aspects:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-beans:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-context:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-core:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-expression:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-jdbc:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-messaging:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-orm:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-test:7.0.9=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-tx:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-tx:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-web:7.0.9=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-webmvc:7.0.9=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-database-commons:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-jdbc:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-junit-jupiter:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-postgresql:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-toxiproxy:2.0.5=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-database-commons:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-jdbc:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-junit-jupiter:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-postgresql:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-toxiproxy:2.0.5=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
org.xmlunit:xmlunit-core:2.10.4=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.yaml:snakeyaml:2.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.yaml:snakeyaml:2.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.core:jackson-core:3.1.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.core:jackson-databind:3.1.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson:jackson-bom:3.1.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
diff --git a/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java b/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java
index 72c94470..a1531483 100644
--- a/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java
+++ b/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java
@@ -257,9 +257,22 @@ class JpaModuleBoundaryTest {
private enum ProductionOnly implements com.tngtech.archunit.core.importer.ImportOption {
INSTANCE;
+ /** The module's own artifacts, minus the ones that are not production. */
+ private static final String MODULE_JAR_PREFIX = "/build/libs/persistence-jpa-";
+
@Override
public boolean includes(Location location) {
- return location.contains("/classes/java/main/");
+ if (location.contains("/classes/java/main/")) {
+ return true;
+ }
+ // Since this leaf adopted java-test-fixtures (ADR-BUILD-001) its own test classpath carries
+ // the module as a jar rather than as build/classes/java/main — the fixtures' self-dependency
+ // on the main component resolves to the jar variant. Matching only the class directory made
+ // this corpus empty, and the non-vacuity check above is the only reason that surfaced as a
+ // failure rather than as four rules quietly passing over nothing.
+ return location.contains(MODULE_JAR_PREFIX)
+ && !location.contains("-test-fixtures.jar")
+ && !location.contains("-testkit.jar");
}
}
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java
diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java
similarity index 100%
rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java
rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java
diff --git a/src/adapter/outbound/persistence-mongo/build.gradle b/src/adapter/outbound/persistence-mongo/build.gradle
index 80fc55f7..cfbb6be7 100644
--- a/src/adapter/outbound/persistence-mongo/build.gradle
+++ b/src/adapter/outbound/persistence-mongo/build.gradle
@@ -1,3 +1,7 @@
+// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. Applied here rather than
+// from the root, the way the GraphQL leaf does: only a leaf that has shared test code needs it.
+apply plugin: 'java-test-fixtures'
+
// MongoDB Document Persistence Platform leaf — see
// docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md (design package)
// and docs/mongodb/repository-adaptation.md (how the design's 19 stable + 12 advanced library
@@ -30,7 +34,7 @@ dependencies {
// The design's module dependency table is enforced as package rules, so ArchUnit is what keeps
// "packages instead of modules" from meaning "no boundary at all".
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
+ testImplementation libs.archunit.junit5
testImplementation 'io.projectreactor:reactor-test'
// Real replica set / failover / migration lanes (design §29). Test-scoped so no production
// package can reach a container fixture.
@@ -40,32 +44,43 @@ dependencies {
testImplementation 'org.testcontainers:testcontainers-toxiproxy'
}
-// The testkit is its own source set rather than part of `test` because several lanes consume it and
-// because the design forbids a production module from depending on the testkit. Declaring its
-// dependencies only on the test configurations gives that guarantee without a new Gradle project.
-strictTestLanes {
- // The testkit compiles against exactly what a test does: `implementation` inheritance runs
- // through testImplementation, so this is the module's own dependencies plus the test libraries.
- sourceSet('testkit') { compilesAgainst 'main' }
- sourceSet('mongoPerformanceTest') { compilesAgainst 'main', 'testkit' }
-}
-
-// Every test lane compiles and runs against the testkit.
+// Shared test code lives in src/testFixtures, per ADR-BUILD-001. This leaf is the first migration
+// off `ca.testkit-publisher`: one leaf, two lanes, no cross-module consumer — the proof that the
+// path works before the published testkits follow.
//
-// No publishAs: this leaf's testkit is consumed inside the leaf and is not offered to the
-// composition root, unlike the JPA one whose ArchUnit rule pack the root applies to the production
-// graph. Publishing is opt-in in the convention precisely so that difference stays a decision.
-testkitPublisher {
- consumedBy 'test'
+// `java-test-fixtures` creates the source set and puts it on `test`'s classpath itself, so neither a
+// `sourceSet('testFixtures')` declaration nor a `testFixturesPublisher { consumedBy 'test' }` is needed.
+// The performance lane is not `test` and still has to say it consumes the fixtures, which is what
+// `compilesAgainst` is for — it looks a source set up rather than creating one, so naming a
+// plugin-created set works unchanged.
+//
+// What is given up: the convention's opt-in publishing. This leaf's fixtures are now a consumable
+// variant whether or not anybody asks for them. ADR-BUILD-001 records that as an accepted loss —
+// unconsumed fixtures are unconsumed, and a leaf that genuinely must not offer them needs a module,
+// not a third convention.
+strictTestLanes {
+ sourceSet('mongoPerformanceTest') { compilesAgainst 'main', 'testFixtures' }
}
+// The fixtures declare what they compile against, rather than inheriting the test configuration.
+//
+// The `testkit` source set used to extend `testImplementation`, so it silently saw every test
+// library this leaf declared. That is convenient and it is also how a fixture acquires a dependency
+// nobody chose for it. Listing them here is three more lines and makes the fixtures' own surface
+// reviewable — the same model the GraphQL leaf already uses.
dependencies {
- testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
- testkitImplementation 'io.projectreactor:reactor-test'
- testkitImplementation 'org.testcontainers:testcontainers'
- testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter'
- testkitImplementation 'org.testcontainers:testcontainers-mongodb'
- testkitImplementation 'org.testcontainers:testcontainers-toxiproxy'
+ testFixturesImplementation libs.archunit.junit5
+ testFixturesImplementation 'io.projectreactor:reactor-test'
+ testFixturesImplementation 'org.assertj:assertj-core'
+ testFixturesImplementation 'org.mongodb:bson'
+ // The ArchUnit rule pack names Spring Data's Repository marker in a rule, so it needs the
+ // type on its compile classpath even though it never calls it.
+ testFixturesImplementation 'org.springframework.data:spring-data-commons'
+ testFixturesImplementation 'org.testcontainers:testcontainers'
+ testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter'
+ testFixturesImplementation 'org.testcontainers:testcontainers-mongodb'
+ testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy'
+ testFixturesImplementation libs.toxiproxy.java
}
// Pinned server images. The design forbids `latest` for a certification lane (Task 44): a mutable
diff --git a/src/adapter/outbound/persistence-mongo/gradle.lockfile b/src/adapter/outbound/persistence-mongo/gradle.lockfile
index 330081a7..d0f37f07 100644
--- a/src/adapter/outbound/persistence-mongo/gradle.lockfile
+++ b/src/adapter/outbound/persistence-mongo/gradle.lockfile
@@ -2,82 +2,83 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
-ch.qos.logback:logback-classic:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-ch.qos.logback:logback-core:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.21=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.github.docker-java:docker-java-api:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.docker-java:docker-java-transport-zerodep:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.docker-java:docker-java-transport:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+ch.qos.logback:logback-classic:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+ch.qos.logback:logback-core:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.fasterxml.jackson.core:jackson-annotations:2.21=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.github.docker-java:docker-java-api:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.docker-java:docker-java-transport-zerodep:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.docker-java:docker-java-transport:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
com.github.spotbugs:spotbugs:4.10.2=spotbugs
com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs
-com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.auto:auto-common:1.2.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.auto:auto-common:1.2.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs
-com.google.code.gson:gson:2.13.2=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.code.gson:gson:2.13.2=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath
-com.google.errorprone:error_prone_annotations:2.41.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.google.errorprone:error_prone_annotations:2.41.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
-com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.guava:guava:33.5.0-jre=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:guava:33.5.0-jre=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.google.guava:guava:33.6.0-jre=checkstyle
-com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
com.jayway.jsonpath:json-path:2.10.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
-com.tngtech.archunit:archunit-junit5-api:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine:1.3.0=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit-junit5:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-com.tngtech.archunit:archunit:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-api:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=mongoPerformanceTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine:1.3.0=mongoPerformanceTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit-junit5:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+com.tngtech.archunit:archunit:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
-commons-codec:commons-codec:1.19.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-codec:commons-codec:1.19.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
-commons-io:commons-io:2.20.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-io:commons-io:2.20.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
commons-io:commons-io:2.21.0=spotbugs
-commons-logging:commons-logging:1.3.6=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+commons-logging:commons-logging:1.3.6=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+eu.rekawek.toxiproxy:toxiproxy-java:2.1.7=testFixturesCompileClasspath,testFixturesRuntimeClasspath
info.picocli:picocli:4.7.7=checkstyle
-io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-io.micrometer:micrometer-commons:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-core:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.micrometer:micrometer-observation:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor:reactor-core:3.8.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-io.projectreactor:reactor-test:3.8.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+io.micrometer:micrometer-commons:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-core:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.micrometer:micrometer-observation:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor:reactor-core:3.8.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+io.projectreactor:reactor-test:3.8.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.1.4=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
+javax.inject:javax.inject:1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
jaxen:jaxen:2.0.6=spotbugs
net.bytebuddy:byte-buddy-agent:1.17.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.bytebuddy:byte-buddy:1.17.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-net.java.dev.jna:jna:5.18.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.bytebuddy:byte-buddy:1.17.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+net.java.dev.jna:jna:5.18.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.minidev:accessors-smart:2.6.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.minidev:json-smart:2.6.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
org.antlr:antlr4-runtime:4.13.2=checkstyle
org.apache.bcel:bcel:6.12.0=spotbugs
-org.apache.commons:commons-compress:1.28.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.apache.commons:commons-lang3:3.20.0=checkstyle,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-compress:1.28.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.commons:commons-lang3:3.20.0=checkstyle,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.commons:commons-text:1.15.0=spotbugs
org.apache.commons:commons-text:1.3=checkstyle
org.apache.httpcomponents:httpclient:4.5.13=checkstyle
org.apache.httpcomponents:httpcore:4.4.16=checkstyle
-org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.logging.log4j:log4j-core:2.25.5=spotbugs
-org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
@@ -87,7 +88,7 @@ org.apache.tomcat.embed:tomcat-embed-el:11.0.24=mongoPerformanceTestCompileClass
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
org.apiguardian:apiguardian-api:1.1.2=mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
-org.assertj:assertj-core:3.27.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.assertj:assertj-core:3.27.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.awaitility:awaitility:4.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
@@ -95,29 +96,29 @@ org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
org.dom4j:dom4j:2.2.0=spotbugs
org.hamcrest:hamcrest:3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.hdrhistogram:HdrHistogram:2.2.2=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.hdrhistogram:HdrHistogram:2.2.2=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
-org.jetbrains:annotations:17.0.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,mongoPerformanceTestAnnotationProcessor,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
+org.jetbrains:annotations:17.0.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,mongoPerformanceTestAnnotationProcessor,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.0.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit.jupiter:junit-jupiter:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-commons:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.junit.platform:junit-platform-engine:6.0.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-commons:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit.platform:junit-platform-engine:6.0.3=mongoPerformanceTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.0.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.junit:junit-bom:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.junit:junit-bom:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
-org.latencyutils:LatencyUtils:2.0.3=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.latencyutils:LatencyUtils:2.0.3=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
org.mockito:mockito-core:5.20.0=mockitoAgent,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.20.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.mongodb:bson-record-codec:5.6.5=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.mongodb:bson:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.mongodb:mongodb-driver-core:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.mongodb:mongodb-driver-reactivestreams:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.mongodb:mongodb-driver-sync:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.mongodb:bson-record-codec:5.6.5=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
+org.mongodb:bson:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.mongodb:mongodb-driver-core:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.mongodb:mongodb-driver-reactivestreams:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.mongodb:mongodb-driver-sync:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.objenesis:objenesis:3.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath
-org.opentest4j:opentest4j:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.opentest4j:opentest4j:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
org.osgi:org.osgi.resource:1.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath
@@ -128,64 +129,64 @@ org.ow2.asm:asm-tree:9.10.1=spotbugs
org.ow2.asm:asm-util:9.10.1=spotbugs
org.ow2.asm:asm:9.10.1=spotbugs
org.ow2.asm:asm:9.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.pcollections:pcollections:4.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor
-org.reactivestreams:reactive-streams:1.0.4=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.pcollections:pcollections:4.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor
+org.reactivestreams:reactive-streams:1.0.4=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
-org.rnorth.duct-tape:duct-tape:1.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.rnorth.duct-tape:duct-tape:1.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.skyscreamer:jsonassert:1.5.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.slf4j:slf4j-api:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.slf4j:slf4j-api:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.18=checkstyle
-org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor
-org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-http-converter:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-jackson:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-reactor:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-reactor:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-resttestclient:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-servlet:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-data-mongodb-reactive:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-data-mongodb-reactive:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-jackson:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-tomcat:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-starter-webmvc:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-tomcat:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-web-server:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework.boot:spring-boot-webmvc:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.boot:spring-boot:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.data:spring-data-commons:4.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework.data:spring-data-mongodb:5.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-aop:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-beans:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-context:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-core:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-expression:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.boot:spring-boot:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.data:spring-data-commons:4.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework.data:spring-data-mongodb:5.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-aop:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-beans:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-context:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-core:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-expression:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-test:7.0.9=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.springframework:spring-tx:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.springframework:spring-tx:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-web:7.0.9=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.springframework:spring-webmvc:7.0.9=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-junit-jupiter:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-mongodb:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers-toxiproxy:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.testcontainers:testcontainers:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-junit-jupiter:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-mongodb:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers-toxiproxy:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.testcontainers:testcontainers:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
org.xmlunit:xmlunit-core:2.10.4=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
-org.yaml:snakeyaml:2.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
+org.yaml:snakeyaml:2.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.core:jackson-core:3.1.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson.core:jackson-databind:3.1.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
tools.jackson:jackson-bom:3.1.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java
diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java
similarity index 100%
rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java
rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java
diff --git a/src/app-bootstrap/CLAUDE.md b/src/app-bootstrap/CLAUDE.md
index 49c2f28c..58cf3f81 100644
--- a/src/app-bootstrap/CLAUDE.md
+++ b/src/app-bootstrap/CLAUDE.md
@@ -30,6 +30,57 @@ Package root: `dev.caskeleton.bootstrap`.
- Concrete business policy.
- Controllers, use cases, repository adapters, or mappers authored directly under bootstrap.
- Settings classes that implement business logic.
+- Implementing an `application-core` or `domain-core` port. A class that implements one is an
+ outbound adapter and belongs in an `adapter/outbound/*` leaf, however its dependencies are
+ configured — `CompositionRootBoundaryArchitectureTest` enforces this and holds the one documented
+ exception.
+- Raising a startup failure with `throw new IllegalStateException(...)`. Use `StartupFailures`
+ (`envValidation` / `profileMismatch` / `requiredAdapterDisabled` / `migrationFailed`), so the
+ failure carries its sysexits exit code and structured log fields —
+ `StartupFailureContractArchitectureTest` enforces this.
+- Answering "is this production?" locally. `dev.caskeleton.shared.runtime.RuntimeEnvironment` owns
+ the profile vocabulary for the whole repository.
+- Depending on `org.testcontainers` from `src/test`. Real-service tests belong to
+ `src/integrationTest`; see `docs/testing/TESTING_STRATEGY.md`.
+
+## Package layout
+
+The rules below exist because their absence is what produced the drift this module was reviewed for
+(`docs/reviews/2026-09-07-app-bootstrap-module-code-review.md`, BOOT-006 / BOOT-007). Half the
+capabilities lived under `autoconfigure/` and half at the package root; `Settings` classes were split
+between a `settings/` package and their feature packages with no stated rule; and the same
+decomposition problem was solved two different ways in the same leaf — Notification as nine focused
+configurations, Redis as one 376-line class.
+
+- **A capability's assembly lives in `autoconfigure//`.** Everything a deployment can
+ switch on is a capability: `fileserver`, `httpclient`, `jpa`, `redis`, `notification`, `mongo`,
+ `outbox`, `tracing`, `idempotency`, `lock`, `messaging`.
+- **`Settings` lives beside its capability**, not in `settings/`. The `settings/` package
+ holds only settings that belong to no capability — `BootstrapSettings`, `LoggingSettings`,
+ `PrivacySettings`, `RuntimeSafetySettings`.
+- **`runtime/` holds guards that run whatever is switched on.** Subject matter does not decide
+ ownership: `JpaSchemaSafetyValidator` reads JPA properties but runs when JPA is off, which is the
+ whole point of it, so it is a runtime guard and not part of the JPA capability. Moving guards like
+ it into `autoconfigure/jpa/` made `FiveAdapterOffInventoryTest` fail — correctly, because that test
+ asserts a switched-off capability owns no beans, and the package is how it classifies them.
+- **`runtime/startup/` holds the startup failure vocabulary** — phases, error codes, exceptions, the
+ `StartupFailures` factory, `StartupValidationContext`, and `StartupOrder`.
+
+## Decomposition threshold
+
+State it rather than leaving it to taste, because "it felt long enough" is what produced two
+different answers for the same question.
+
+- One `@Configuration` holds one assembly concern. Split when it passes roughly **eight `@Bean`
+ methods**, or as soon as it assembles two unrelated port families — whichever comes first.
+- One `@ConfigurationProperties` class owns one capability namespace.
+- A capability that needs more than one configuration gets a root class holding the master switch,
+ `@EnableConfigurationProperties`, and an `@Import` of the per-concern classes. Scan nothing:
+ `@Import` from a gated root is what keeps the master switch load-bearing rather than advisory.
+ `autoconfigure/redis/RedisCapabilityConfig` and `notification/NotificationRootAutoConfiguration`
+ are the two worked examples.
+- Shared helpers of a split capability get their own type named for what they are, not a `*Util`
+ bucket — `autoconfigure/redis/RedisKeys` is the reference.
## Test
diff --git a/src/app-bootstrap/build.gradle b/src/app-bootstrap/build.gradle
index 6d70ffb7..046c3d5b 100644
--- a/src/app-bootstrap/build.gradle
+++ b/src/app-bootstrap/build.gradle
@@ -22,6 +22,23 @@ configurations {
}
}
+// The infrastructure lane. Testcontainers-backed tests boot a real PostgreSQL, so they belong to a
+// source set of their own rather than to `test`: the ordinary focused command for this leaf —
+// `./gradlew :app-bootstrap:test` — must not require a Docker daemon, and a leaf whose fastest
+// feedback loop starts a database is a leaf nobody runs the fast loop on. This is the same shape
+// persistence-jpa, web, websocket and objectstorage already use, declared through the repository's
+// own strictTestLanes DSL rather than by hand.
+strictTestLanes {
+ sourceSet('integrationTest') {
+ compilesAgainst 'main'
+ inherits 'implementation', 'compileOnly', 'runtimeOnly', 'annotationProcessor'
+ }
+ lane('integrationTest') {
+ description = 'Testcontainers-backed real-PostgreSQL integration contracts.'
+ sourceSet = 'integrationTest'
+ }
+}
+
sourceSets {
sampleOffTest {
java.srcDirs = sourceSets.test.java.srcDirs
@@ -59,8 +76,8 @@ dependencies {
// The JPA rule pack, for the production architecture suite. Test scope only: the rules are
// ArchUnit assertions, and the composition root is the one place that can see every runtime
// leaf at once — which is what makes it the right consumer and the wrong shipper.
- testImplementation(project(path: ':adapter:outbound:persistence-jpa', configuration: 'jpaTestkit'))
- testImplementation(project(path: ':adapter:inbound:web', configuration: 'webTestkit'))
+ testImplementation(testFixtures(project(':adapter:outbound:persistence-jpa')))
+ testImplementation(testFixtures(project(':adapter:inbound:web')))
// Shipped so an operator can enable it with one environment variable. An adapter that is
// absent is not the same contract as one that is off: absence cannot be reversed at deploy time,
// and it hides every gating defect, because code that is not there holds no beans whatever its
@@ -100,7 +117,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-validation'
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
- implementation 'me.paulschwarz:spring-dotenv:4.0.0'
+ implementation libs.spring.dotenv
// Boot 4 Flyway API/autoconfiguration: the composition root drives startup migration
// (MigrationStartupConfig). See README.
implementation 'org.springframework.boot:spring-boot-flyway'
@@ -128,9 +145,17 @@ dependencies {
// test-only: @WithMockUser for the actuator security authorization tests. See README.
testImplementation 'org.springframework.security:spring-security-test'
- // test-only: Testcontainers PostgreSQL for the outbox contract tests.
- testImplementation 'org.testcontainers:testcontainers-postgresql'
- testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
+ // integration-lane-only: Testcontainers PostgreSQL. Declared here and NOT on testImplementation
+ // so `:app-bootstrap:test` cannot start a container — the source-set boundary is what enforces
+ // BOOT-014, and a dependency left on the test classpath would let the boundary be crossed back
+ // by a single import.
+ integrationTestImplementation 'org.testcontainers:testcontainers-postgresql'
+ integrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter'
+ // test-only, compile-only: TestcontainersUsingFixture declares a PostgreSQLContainer *field type*
+ // as the non-vacuity positive control for the Testcontainers-ban ArchUnit rule. ClassFileImporter
+ // reads its bytecode without resolving or constructing the field, so the type is never needed at
+ // runtime — the same compileOnly shape the other violation fixtures use.
+ testCompileOnly 'org.testcontainers:testcontainers-postgresql'
// test-only: Boot 4 split JPA slice annotations into dedicated test modules.
testImplementation 'org.springframework.boot:spring-boot-data-jpa-test'
// test-only: PG JDBC driver (the vendor module's runtimeOnly does not leak here). See README.
@@ -139,10 +164,11 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'com.zaxxer:HikariCP'
- // test-only: JdbcLockRegistry for the distributed-lock contract test (two simulated instances). See README.
- testImplementation 'org.springframework.integration:spring-integration-jdbc'
+ // integration-lane-only: JdbcLockRegistry for the distributed-lock contract test (two simulated
+ // instances). Nothing under src/test imports org.springframework.integration.
+ integrationTestImplementation 'org.springframework.integration:spring-integration-jdbc'
- testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
+ testImplementation libs.archunit.junit5
// test-only: jackson-databind for the deserialization-policy boundary test. See README.
testImplementation 'org.springframework.boot:spring-boot-starter-json'
// test-only: parses checked-in Messaging capability registries for the fail-closed drift gate.
@@ -165,7 +191,7 @@ dependencies {
implementation 'org.apache.kafka:kafka-clients' // org.apache.kafka..
testCompileOnly 'jakarta.ws.rs:jakarta.ws.rs-api' // jakarta.ws.rs..
// test-only: @RefreshScope for the no-refresh-scope violation fixture (version pinned; not in the BOM). See README.
- testCompileOnly 'org.springframework.cloud:spring-cloud-context:4.1.4' // org.springframework.cloud.context..
+ testCompileOnly libs.spring.cloud.context // org.springframework.cloud.context..
// analysis-only: SpotBugs resolves the full type hierarchy of everything on the auxiliary
// classpath, and spring-beans/spring-context/spring-web each ship an optional Groovy integration
// class (GroovyBeanDefinitionReader, GenericGroovyApplicationContext, GroovyWebApplicationContext).
@@ -179,11 +205,11 @@ dependencies {
testCompileOnly 'org.apache.groovy:groovy' // groovy.lang.GroovyObject
// JSON log encoder; implementation (not runtimeOnly) because StartupFailures uses StructuredArguments at compile time. See README.
- implementation 'net.logstash.logback:logstash-logback-encoder:8.0'
+ implementation libs.logstash.logback.encoder
// test-only: ApprovalTests JSON snapshot verification for the contract-verification suite
// (feature-contract-verification-test-suite D7 — envelope/error shape snapshots). See README.
- testImplementation 'com.approvaltests:approvaltests:31.0.0'
+ testImplementation libs.approvaltests
// test-only: JUnit Platform Test Kit — proves optional-adapter tests report SKIPPED (never FAILED)
// when their enable-flag env var is unset (feature-contract-verification-test-suite D3, Claims #7). See README.
testImplementation 'org.junit.platform:junit-platform-testkit'
diff --git a/src/app-bootstrap/gradle.lockfile b/src/app-bootstrap/gradle.lockfile
index aebfadb4..655a2ba3 100644
--- a/src/app-bootstrap/gradle.lockfile
+++ b/src/app-bootstrap/gradle.lockfile
@@ -1,115 +1,116 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
-aopalliance:aopalliance:1.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-at.yawk.lz4:lz4-java:1.10.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+aopalliance:aopalliance:1.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+at.yawk.lz4:lz4-java:1.10.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
ch.qos.logback:logback-classic:1.5.34=sampleFixture
-ch.qos.logback:logback-classic:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+ch.qos.logback:logback-classic:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
ch.qos.logback:logback-core:1.5.34=sampleFixture
-ch.qos.logback:logback-core:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.approvaltests:approvaltests-util:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.approvaltests:approvaltests:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.ethlo.time:itu:1.14.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+ch.qos.logback:logback-core:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.approvaltests:approvaltests-util:31.0.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.approvaltests:approvaltests:31.0.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.ethlo.time:itu:1.14.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.21.4=sampleFixture
-com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.21.4=sampleFixture
-com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.21.4=sampleFixture
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.4=sampleFixture
-com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.4=sampleFixture
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.4=sampleFixture
-com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.4=sampleFixture
com.fasterxml.jackson:jackson-bom:2.21.4=sampleFixture
-com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.fasterxml:classmate:1.7.3=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.github.docker-java:docker-java-api:3.7.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.github.docker-java:docker-java-transport-zerodep:3.7.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.github.docker-java:docker-java-transport:3.7.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.fasterxml:classmate:1.7.3=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.github.docker-java:docker-java-api:3.7.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.github.docker-java:docker-java-transport-zerodep:3.7.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.github.docker-java:docker-java-transport:3.7.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.f4b6a3:uuid-creator:6.1.1=sampleFixture,testRuntimeClasspath
-com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.github.luben:zstd-jni:1.5.6-10=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.github.luben:zstd-jni:1.5.6-10=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
com.github.spotbugs:spotbugs:4.10.2=spotbugs
-com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
+com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
com.google.android:annotations:4.1.1.4=conditionalTransportTestRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.41.0=conditionalTransportTestRuntimeClasspath
-com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.google.auto:auto-common:1.2.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.auto:auto-common:1.2.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
com.google.code.findbugs:jsr305:3.0.2=checkstyle,conditionalTransportTestRuntimeClasspath,spotbugs
-com.google.code.gson:gson:2.13.2=conditionalTransportTestRuntimeClasspath,spotbugs
-com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
-com.google.errorprone:error_prone_annotations:2.41.0=conditionalTransportTestRuntimeClasspath,spotbugs
+com.google.code.gson:gson:2.13.2=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,spotbugs,testRuntimeClasspath
+com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+com.google.errorprone:error_prone_annotations:2.41.0=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,spotbugs,testRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.47.0=checkstyle
-com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
com.google.guava:failureaccess:1.0.2=conditionalTransportTestRuntimeClasspath
-com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.2.1-jre=conditionalTransportTestRuntimeClasspath
-com.google.guava:guava:33.5.0-jre=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.guava:guava:33.5.0-jre=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.6.0-jre=checkstyle
-com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
com.google.j2objc:j2objc-annotations:2.8=conditionalTransportTestRuntimeClasspath
-com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
com.google.protobuf:protobuf-java-util:3.25.5=conditionalTransportTestRuntimeClasspath
com.google.protobuf:protobuf-java:3.25.5=conditionalTransportTestRuntimeClasspath
-com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-com.graphql-java:graphql-java:25.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.graphql-java:java-dataloader:6.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.h2database:h2:2.4.240=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+com.graphql-java:graphql-java:25.0=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.graphql-java:java-dataloader:6.0.0=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.h2database:h2:2.4.240=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
-com.jayway.jsonpath:json-path:2.10.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.networknt:json-schema-validator:3.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.nimbusds:content-type:2.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.nimbusds:lang-tag:1.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.jayway.jsonpath:json-path:2.10.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.networknt:json-schema-validator:3.0.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.nimbusds:content-type:2.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.nimbusds:lang-tag:1.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.nimbusds:nimbus-jose-jwt:9.37.4=sampleFixture
-com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
-com.rabbitmq:amqp-client:5.27.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.squareup.okhttp3:okhttp-jvm:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.rabbitmq:amqp-client:5.27.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.squareup.okhttp3:okhttp-jvm:5.2.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.squareup.okhttp3:okhttp:4.12.0=sampleFixture
-com.squareup.okhttp3:okhttp:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.squareup.okio:okio-jvm:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.squareup.okhttp3:okhttp:5.2.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.squareup.okio:okio-jvm:3.16.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.squareup.okio:okio-jvm:3.6.0=sampleFixture
-com.squareup.okio:okio:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.squareup.okio:okio:3.16.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
com.squareup.okio:okio:3.6.0=sampleFixture
-com.sun.istack:istack-commons-runtime:4.1.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-api:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.tngtech.archunit:archunit-junit5-engine:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath
-com.tngtech.archunit:archunit-junit5:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.tngtech.archunit:archunit:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.vaadin.external.google:android-json:0.0.20131108.vaadin1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.sun.istack:istack-commons-runtime:4.1.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-api:1.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.tngtech.archunit:archunit-junit5-engine:1.3.0=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+com.tngtech.archunit:archunit-junit5:1.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.tngtech.archunit:archunit:1.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.vaadin.external.google:android-json:0.0.20131108.vaadin1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
com.zaxxer:HikariCP:6.3.3=sampleFixture
-com.zaxxer:HikariCP:7.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.zaxxer:HikariCP:7.0.2=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-beanutils:commons-beanutils:1.11.0=checkstyle
-commons-codec:commons-codec:1.19.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+commons-codec:commons-codec:1.19.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-collections:commons-collections:3.2.2=checkstyle
-commons-io:commons-io:2.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+commons-io:commons-io:2.20.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-io:commons-io:2.21.0=spotbugs
-commons-logging:commons-logging:1.3.6=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+commons-logging:commons-logging:1.3.6=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
info.picocli:picocli:4.7.7=checkstyle
-io.cloudevents:cloudevents-api:4.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.cloudevents:cloudevents-core:4.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.cdimascio:dotenv-java:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
-io.github.resilience4j:resilience4j-bulkhead:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.resilience4j:resilience4j-core:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.resilience4j:resilience4j-micrometer:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.resilience4j:resilience4j-ratelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.resilience4j:resilience4j-retry:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.github.resilience4j:resilience4j-timelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.cloudevents:cloudevents-api:4.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.cloudevents:cloudevents-core:4.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.cdimascio:dotenv-java:3.0.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+io.github.resilience4j:resilience4j-bulkhead:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.resilience4j:resilience4j-core:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.resilience4j:resilience4j-micrometer:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.resilience4j:resilience4j-ratelimiter:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.resilience4j:resilience4j-retry:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.github.resilience4j:resilience4j-timelimiter:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.grpc:grpc-api:1.68.1=conditionalTransportTestRuntimeClasspath
io.grpc:grpc-context:1.68.1=conditionalTransportTestRuntimeClasspath
io.grpc:grpc-core:1.68.1=conditionalTransportTestRuntimeClasspath
@@ -119,399 +120,403 @@ io.grpc:grpc-protobuf:1.68.1=conditionalTransportTestRuntimeClasspath
io.grpc:grpc-services:1.68.1=conditionalTransportTestRuntimeClasspath
io.grpc:grpc-stub:1.68.1=conditionalTransportTestRuntimeClasspath
io.grpc:grpc-util:1.68.1=conditionalTransportTestRuntimeClasspath
-io.lettuce:lettuce-core:6.8.2.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.lettuce:lettuce-core:6.8.2.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.micrometer:context-propagation:1.1.4=sampleFixture
-io.micrometer:context-propagation:1.2.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:context-propagation:1.2.1=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-commons:1.15.12=sampleFixture
-io.micrometer:micrometer-commons:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-commons:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-core:1.15.12=sampleFixture
-io.micrometer:micrometer-core:1.16.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-core:1.16.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-jakarta9:1.15.12=sampleFixture
-io.micrometer:micrometer-jakarta9:1.16.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-jakarta9:1.16.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-observation:1.15.12=sampleFixture
-io.micrometer:micrometer-observation:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-observation:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-registry-prometheus:1.15.12=sampleFixture
-io.micrometer:micrometer-registry-prometheus:1.16.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-registry-prometheus:1.16.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-tracing-bridge-otel:1.5.12=sampleFixture
-io.micrometer:micrometer-tracing-bridge-otel:1.6.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.micrometer:micrometer-tracing-bridge-otel:1.6.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.micrometer:micrometer-tracing:1.5.12=sampleFixture
-io.micrometer:micrometer-tracing:1.6.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-io.netty:netty-buffer:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-base:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-classes-quic:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-compression:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-dns:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-http2:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-http3:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-http:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-marshalling:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-native-quic:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-protobuf:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec-socks:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-codec:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-handler-proxy:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-handler:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-resolver-dns-native-macos:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-resolver-dns:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-resolver:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-transport-classes-epoll:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-transport-native-epoll:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-transport-native-unix-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.netty:netty-transport:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.micrometer:micrometer-tracing:1.6.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.netty:netty-buffer:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-base:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-classes-quic:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-compression:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-dns:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-http2:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-http3:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-http:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-marshalling:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-native-quic:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-protobuf:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec-socks:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-codec:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-common:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-handler-proxy:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-handler:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-resolver-dns-native-macos:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-resolver-dns:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-resolver:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-transport-classes-epoll:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-transport-native-epoll:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-transport-native-unix-common:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.netty:netty-transport:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.opentelemetry.semconv:opentelemetry-semconv:1.32.0=sampleFixture
-io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-api:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-context:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-common:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-exporter-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-common:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-otlp-common:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-otlp:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-extension-trace-propagators:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-metrics:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-trace:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk:1.49.0=sampleFixture
-io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.perfmark:perfmark-api:0.27.0=conditionalTransportTestRuntimeClasspath
-io.projectreactor.netty:reactor-netty-core:1.3.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-io.projectreactor.netty:reactor-netty-http:1.3.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.projectreactor.netty:reactor-netty-core:1.3.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.projectreactor.netty:reactor-netty-http:1.3.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.projectreactor:reactor-core:3.7.19=sampleFixture
-io.projectreactor:reactor-core:3.8.7=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.projectreactor:reactor-core:3.8.7=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-config:1.3.10=sampleFixture
-io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-core:1.3.10=sampleFixture
-io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-exposition-formats:1.3.10=sampleFixture
-io.prometheus:prometheus-metrics-exposition-formats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-formats:1.4.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-exposition-textformats:1.3.10=sampleFixture
-io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-model:1.3.10=sampleFixture
-io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.prometheus:prometheus-metrics-tracer-common:1.3.10=sampleFixture
-io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
io.smallrye:jandex:3.2.0=sampleFixture
+io.smallrye:jandex:3.3.2=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-annotations-jakarta:2.2.29=sampleFixture
-io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-core-jakarta:2.2.29=sampleFixture
-io.swagger.core.v3:swagger-core-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+io.swagger.core.v3:swagger-core-jakarta:2.2.38=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
io.swagger.core.v3:swagger-models-jakarta:2.2.29=sampleFixture
-io.swagger.core.v3:swagger-models-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-jakarta.activation:jakarta.activation-api:2.1.4=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.swagger.core.v3:swagger-models-jakarta:2.2.38=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+jakarta.activation:jakarta.activation-api:2.1.4=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.annotation:jakarta.annotation-api:2.1.1=sampleFixture
-jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-jakarta.inject:jakarta.inject-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-jakarta.mail:jakarta.mail-api:2.1.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+jakarta.inject:jakarta.inject-api:2.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+jakarta.mail:jakarta.mail-api:2.1.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
jakarta.persistence:jakarta.persistence-api:3.1.0=sampleFixture
-jakarta.persistence:jakarta.persistence-api:3.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-jakarta.transaction:jakarta.transaction-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+jakarta.persistence:jakarta.persistence-api:3.2.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+jakarta.servlet:jakarta.servlet-api:6.1.0=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+jakarta.transaction:jakarta.transaction-api:2.0.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.validation:jakarta.validation-api:3.0.2=sampleFixture
-jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-jakarta.websocket:jakarta.websocket-api:2.2.0=sampleOffTestCompileClasspath,testCompileClasspath
-jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=sampleOffTestCompileClasspath,testCompileClasspath
-jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+jakarta.websocket:jakarta.websocket-api:2.2.0=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+javax.inject:javax.inject:1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
jaxen:jaxen:2.0.6=spotbugs
-me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-net.bytebuddy:byte-buddy-agent:1.17.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-net.bytebuddy:byte-buddy:1.17.8=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-net.java.dev.jna:jna:5.18.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-net.minidev:accessors-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-net.minidev:json-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+net.bytebuddy:byte-buddy-agent:1.17.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+net.bytebuddy:byte-buddy:1.17.8=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+net.java.dev.jna:jna:5.18.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+net.minidev:accessors-smart:2.6.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+net.minidev:json-smart:2.6.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
-ognl:ognl:3.3.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.antlr:antlr4-runtime:4.13.2=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+ognl:ognl:3.3.4=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.antlr:antlr4-runtime:4.13.2=checkstyle,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.bcel:bcel:6.12.0=spotbugs
-org.apache.commons:commons-compress:1.28.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.apache.commons:commons-lang3:3.20.0=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
+org.apache.commons:commons-compress:1.28.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apache.commons:commons-lang3:3.20.0=checkstyle,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.15.0=spotbugs
org.apache.commons:commons-text:1.3=checkstyle
-org.apache.groovy:groovy-bom:5.0.8=sampleOffTestCompileClasspath,testCompileClasspath
-org.apache.groovy:groovy:5.0.8=sampleOffTestCompileClasspath,testCompileClasspath
-org.apache.httpcomponents.client5:httpclient5:5.5.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.apache.httpcomponents.core5:httpcore5:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.apache.groovy:groovy-bom:5.0.8=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.apache.groovy:groovy:5.0.8=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.apache.httpcomponents.client5:httpclient5:5.5.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.apache.httpcomponents.core5:httpcore5:5.3.6=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.13=checkstyle,testRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.16=checkstyle,testRuntimeClasspath
-org.apache.kafka:kafka-clients:4.1.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apache.kafka:kafka-clients:4.1.2=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.logging.log4j:log4j-api:2.24.3=sampleFixture
-org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
+org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
org.apache.logging.log4j:log4j-core:2.25.5=spotbugs
org.apache.logging.log4j:log4j-to-slf4j:2.24.3=sampleFixture
-org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.maven.doxia:doxia-core:1.12.0=checkstyle
org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
org.apache.tomcat.embed:tomcat-embed-core:10.1.55=sampleFixture
-org.apache.tomcat.embed:tomcat-embed-core:11.0.24=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-core:11.0.24=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.tomcat.embed:tomcat-embed-el:10.1.55=sampleFixture
-org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.tomcat.embed:tomcat-embed-websocket:10.1.55=sampleFixture
-org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
-org.apiguardian:apiguardian-api:1.1.2=conditionalTransportTestCompileClasspath,functionalTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
-org.aspectj:aspectjweaver:1.9.25.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.assertj:assertj-core:3.27.7=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.attoparser:attoparser:2.0.7.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.awaitility:awaitility:4.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.apiguardian:apiguardian-api:1.1.2=conditionalTransportTestCompileClasspath,functionalTestCompileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.aspectj:aspectjweaver:1.9.25.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.assertj:assertj-core:3.27.7=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.attoparser:attoparser:2.0.7.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.awaitility:awaitility:4.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.42.0=conditionalTransportTestRuntimeClasspath
-org.checkerframework:checker-qual:3.55.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.checkerframework:checker-qual:3.55.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.codehaus.mojo:animal-sniffer-annotations:1.24=conditionalTransportTestRuntimeClasspath
org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
org.dom4j:dom4j:2.2.0=spotbugs
-org.eclipse.angus:angus-activation:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.angus:angus-mail:2.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty.compression:jetty-compression-common:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty:jetty-alpn-client:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty:jetty-client:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty:jetty-http:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty:jetty-io:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.eclipse.jetty:jetty-util:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.flywaydb:flyway-core:11.14.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.eclipse.angus:angus-activation:2.0.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.angus:angus-mail:2.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty.compression:jetty-compression-common:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty:jetty-alpn-client:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty:jetty-client:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty:jetty-http:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty:jetty-io:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.eclipse.jetty:jetty-util:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.flywaydb:flyway-core:11.14.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-core:11.7.2=sampleFixture
-org.flywaydb:flyway-database-postgresql:11.14.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.flywaydb:flyway-database-postgresql:11.14.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:11.7.2=sampleFixture
-org.glassfish.jaxb:jaxb-core:4.0.9=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.glassfish.jaxb:jaxb-runtime:4.0.9=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.glassfish.jaxb:txw2:4.0.9=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.hamcrest:hamcrest:3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.glassfish.jaxb:jaxb-core:4.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.glassfish.jaxb:jaxb-runtime:4.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.glassfish.jaxb:txw2:4.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.hamcrest:hamcrest:3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.hdrhistogram:HdrHistogram:2.2.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.hibernate.common:hibernate-commons-annotations:7.0.3.Final=sampleFixture
-org.hibernate.models:hibernate-models:1.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.hibernate.models:hibernate-models:1.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.hibernate.orm:hibernate-core:6.6.53.Final=sampleFixture
-org.hibernate.orm:hibernate-core:7.2.24.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.hibernate.orm:hibernate-core:7.2.24.Final=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.hibernate.orm:hibernate-envers:7.2.24.Final=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.hibernate.validator:hibernate-validator:8.0.3.Final=sampleFixture
-org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.javassist:javassist:3.28.0-GA=checkstyle
-org.javassist:javassist:3.29.0-GA=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.javassist:javassist:3.29.0-GA=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.25=sampleFixture
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.25=sampleFixture
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.25=sampleFixture
org.jetbrains.kotlin:kotlin-stdlib:1.9.25=sampleFixture
-org.jetbrains.kotlin:kotlin-stdlib:2.2.21=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.jetbrains.kotlin:kotlin-stdlib:2.2.21=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.jetbrains:annotations:13.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture
-org.jetbrains:annotations:17.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,conditionalTransportTestAnnotationProcessor,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,functionalTestCompileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-api:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter-params:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.jupiter:junit-jupiter:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-commons:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-launcher:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit.platform:junit-platform-testkit:6.0.3=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.junit:junit-bom:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.jetbrains:annotations:17.0.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,conditionalTransportTestAnnotationProcessor,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,functionalTestCompileClasspath,integrationTestAnnotationProcessor,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
+org.junit.jupiter:junit-jupiter-api:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.jupiter:junit-jupiter-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.junit.jupiter:junit-jupiter-params:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.jupiter:junit-jupiter:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.platform:junit-platform-commons:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.platform:junit-platform-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.platform:junit-platform-launcher:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit.platform:junit-platform-testkit:6.0.3=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.junit:junit-bom:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:6.1.0=spotbugs
-org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.mockito:mockito-core:5.20.0=mockitoAgent,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.mockito:mockito-junit-jupiter:5.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.mongodb:bson-record-codec:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.mongodb:bson:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.mongodb:mongodb-driver-core:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.mongodb:mongodb-driver-sync:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.objenesis:objenesis:3.3=sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.openapitools:jackson-databind-nullable:0.2.6=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.opentest4j:opentest4j:1.3.0=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
-org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
-org.osgi:org.osgi.resource:1.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
-org.osgi:org.osgi.service.serviceloader:1.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.latencyutils:LatencyUtils:2.0.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.mockito:mockito-core:5.20.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,mockitoAgent,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.mockito:mockito-junit-jupiter:5.20.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.mongodb:bson-record-codec:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.mongodb:bson:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.mongodb:mongodb-driver-core:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.mongodb:mongodb-driver-sync:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.objenesis:objenesis:3.3=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.openapitools:jackson-databind-nullable:0.2.6=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.opentest4j:opentest4j:1.3.0=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.osgi:org.osgi.resource:1.0.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.osgi:org.osgi.service.serviceloader:1.0.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
org.ow2.asm:asm-analysis:9.10.1=spotbugs
org.ow2.asm:asm-commons:9.10.1=spotbugs
org.ow2.asm:asm-tree:9.10.1=spotbugs
org.ow2.asm:asm-util:9.10.1=spotbugs
org.ow2.asm:asm:9.10.1=spotbugs
-org.ow2.asm:asm:9.7.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.pcollections:pcollections:4.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
+org.ow2.asm:asm:9.7.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.pcollections:pcollections:4.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
org.postgresql:postgresql:42.7.11=sampleFixture
-org.postgresql:postgresql:42.7.13=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.reactivestreams:reactive-streams:1.0.4=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.postgresql:postgresql:42.7.13=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.reactivestreams:reactive-streams:1.0.4=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.reflections:reflections:0.10.2=checkstyle
-org.rnorth.duct-tape:duct-tape:1.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.skyscreamer:jsonassert:1.5.3=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.slf4j:slf4j-api:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
+org.rnorth.duct-tape:duct-tape:1.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.skyscreamer:jsonassert:1.5.3=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.slf4j:slf4j-api:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j
org.slf4j:slf4j-simple:2.0.18=checkstyle
org.springdoc:springdoc-openapi-starter-common:2.8.6=sampleFixture
-org.springdoc:springdoc-openapi-starter-common:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springdoc:springdoc-openapi-starter-common:3.0.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6=sampleFixture
-org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.amqp:spring-amqp:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.amqp:spring-rabbit:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.amqp:spring-amqp:4.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.amqp:spring-rabbit:4.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-actuator:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-actuator:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-actuator:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-autoconfigure:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor
-org.springframework.boot:spring-boot-data-commons:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-data-jpa-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-data-jpa:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-data-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-health:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-hibernate:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-http-converter:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-jdbc-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-jpa-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-jpa:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-mail:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-micrometer-metrics:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-micrometer-observation:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-reactor:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-resttestclient:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-servlet:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-data-commons:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-data-jpa-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-data-jpa:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-data-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-health:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-hibernate:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-http-converter:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-jdbc-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-jpa-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-jpa:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-mail:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-micrometer-metrics:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-micrometer-observation:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-reactor:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-resttestclient:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-servlet:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-actuator:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-actuator:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-actuator:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-data-jpa:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-flyway:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-flyway:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-jdbc:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-jdbc:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-jdbc:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-json:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-json:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-json:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-logging:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-mail:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-mail:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-security:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-tomcat:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-validation:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-web:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter-web:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-starter-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-web:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-starter-websocket:4.0.8=conditionalTransportTestRuntimeClasspath
org.springframework.boot:spring-boot-starter:3.5.16=sampleFixture
-org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-web-server:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-webmvc-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.boot:spring-boot-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-web-server:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-webmvc-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.boot:spring-boot-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.boot:spring-boot-websocket:4.0.8=conditionalTransportTestRuntimeClasspath
org.springframework.boot:spring-boot:3.5.16=sampleFixture
-org.springframework.boot:spring-boot:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.cloud:spring-cloud-context:4.1.4=sampleOffTestCompileClasspath,testCompileClasspath
+org.springframework.boot:spring-boot:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.cloud:spring-cloud-context:4.1.4=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
org.springframework.data:spring-data-commons:3.5.13=sampleFixture
-org.springframework.data:spring-data-commons:4.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.data:spring-data-commons:4.0.7=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.data:spring-data-jpa:3.5.13=sampleFixture
-org.springframework.data:spring-data-jpa:4.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.data:spring-data-mongodb:5.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.graphql:spring-graphql:2.0.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.data:spring-data-jpa:4.0.7=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.data:spring-data-mongodb:5.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.graphql:spring-graphql:2.0.5=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.integration:spring-integration-core:6.5.10=sampleFixture
-org.springframework.integration:spring-integration-core:7.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.integration:spring-integration-core:7.0.6=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.integration:spring-integration-jdbc:6.5.10=sampleFixture
-org.springframework.integration:spring-integration-jdbc:7.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.kafka:spring-kafka:4.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.integration:spring-integration-jdbc:7.0.6=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.kafka:spring-kafka:4.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.retry:spring-retry:2.0.13=sampleFixture
org.springframework.security:spring-security-config:6.5.11=sampleFixture
-org.springframework.security:spring-security-config:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-config:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.security:spring-security-core:6.5.11=sampleFixture
-org.springframework.security:spring-security-core:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-core:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.security:spring-security-crypto:6.5.11=sampleFixture
-org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.security:spring-security-oauth2-core:6.5.11=sampleFixture
-org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.security:spring-security-oauth2-jose:6.5.11=sampleFixture
-org.springframework.security:spring-security-oauth2-jose:7.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-oauth2-jose:7.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework.security:spring-security-oauth2-resource-server:6.5.11=sampleFixture
-org.springframework.security:spring-security-oauth2-resource-server:7.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.springframework.security:spring-security-test:7.0.7=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-oauth2-resource-server:7.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-test:7.0.7=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework.security:spring-security-web:6.5.11=sampleFixture
-org.springframework.security:spring-security-web:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework.session:spring-session-core:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework.security:spring-security-web:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework.session:spring-session-core:4.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework:spring-aop:6.2.19=sampleFixture
-org.springframework:spring-aop:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-aop:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-aspects:6.2.19=sampleFixture
-org.springframework:spring-aspects:7.0.9=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-aspects:7.0.9=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-beans:6.2.19=sampleFixture
-org.springframework:spring-beans:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework:spring-context-support:7.0.9=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework:spring-beans:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-context-support:7.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework:spring-context:6.2.19=sampleFixture
-org.springframework:spring-context:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-context:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-core:6.2.19=sampleFixture
-org.springframework:spring-core:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-core:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-expression:6.2.19=sampleFixture
-org.springframework:spring-expression:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-expression:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-jcl:6.2.19=sampleFixture
org.springframework:spring-jdbc:6.2.19=sampleFixture
-org.springframework:spring-jdbc:7.0.9=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-jdbc:7.0.9=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-messaging:6.2.19=sampleFixture
-org.springframework:spring-messaging:7.0.9=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-messaging:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework:spring-orm:6.2.19=sampleFixture
-org.springframework:spring-orm:7.0.9=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework:spring-test:7.0.9=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-orm:7.0.9=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-test:7.0.9=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-tx:6.2.19=sampleFixture
-org.springframework:spring-tx:7.0.9=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-tx:7.0.9=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.springframework:spring-web:6.2.19=sampleFixture
-org.springframework:spring-web:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework:spring-webflux:7.0.9=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework:spring-web:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-webflux:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.springframework:spring-webmvc:6.2.19=sampleFixture
-org.springframework:spring-webmvc:7.0.9=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.springframework:spring-websocket:7.0.9=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,testCompileClasspath
-org.testcontainers:testcontainers-database-commons:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.testcontainers:testcontainers-jdbc:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.testcontainers:testcontainers-junit-jupiter:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.testcontainers:testcontainers-postgresql:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.testcontainers:testcontainers:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.thymeleaf:thymeleaf:3.1.5.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.unbescape:unbescape:1.1.6.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
-org.xerial.snappy:snappy-java:1.1.10.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.springframework:spring-webmvc:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.springframework:spring-websocket:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
+org.testcontainers:testcontainers-database-commons:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.testcontainers:testcontainers-jdbc:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.testcontainers:testcontainers-junit-jupiter:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.testcontainers:testcontainers-postgresql:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.testcontainers:testcontainers-toxiproxy:2.0.5=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.testcontainers:testcontainers:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.thymeleaf:thymeleaf:3.1.5.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.unbescape:unbescape:1.1.6.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.xerial.snappy:snappy-java:1.1.10.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs
-org.xmlunit:xmlunit-core:2.10.4=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.xmlunit:xmlunit-core:2.10.4=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
org.yaml:snakeyaml:2.4=sampleFixture
-org.yaml:snakeyaml:2.5=compileClasspath,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-redis.clients.authentication:redis-authx-core:0.1.1-beta2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
+org.yaml:snakeyaml:2.5=compileClasspath,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+redis.clients.authentication:redis-authx-core:0.1.1-beta2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
software.amazon.awssdk:annotations:2.30.0=testRuntimeClasspath
software.amazon.awssdk:apache-client:2.30.0=testRuntimeClasspath
software.amazon.awssdk:arns:2.30.0=testRuntimeClasspath
@@ -542,7 +547,7 @@ software.amazon.awssdk:sdk-core:2.30.0=testRuntimeClasspath
software.amazon.awssdk:third-party-jackson-core:2.30.0=testRuntimeClasspath
software.amazon.awssdk:utils:2.30.0=testRuntimeClasspath
software.amazon.eventstream:eventstream:1.0.1=testRuntimeClasspath
-tools.jackson.core:jackson-core:3.1.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-tools.jackson.core:jackson-databind:3.1.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
-tools.jackson:jackson-bom:3.1.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+tools.jackson.core:jackson-core:3.1.5=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+tools.jackson.core:jackson-databind:3.1.5=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
+tools.jackson:jackson-bom:3.1.5=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
empty=developmentOnly,testAndDevelopmentOnly
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java
index 4a6bf359..ba72e28f 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java
+++ b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java
@@ -12,8 +12,8 @@ import dev.caskeleton.adapter.outbound.persistence.lock.LockSettings;
import dev.caskeleton.application.lock.DistributedLock;
import dev.caskeleton.application.lock.DistributedLockPort;
import dev.caskeleton.application.lock.LockAcquisitionTimeoutException;
-import dev.caskeleton.bootstrap.lock.DistributedLockConfig;
-import dev.caskeleton.bootstrap.lock.MeteredDistributedLockPort;
+import dev.caskeleton.bootstrap.autoconfigure.lock.DistributedLockConfig;
+import dev.caskeleton.bootstrap.autoconfigure.lock.MeteredDistributedLockPort;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import java.time.Duration;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/package-info.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/package-info.java
similarity index 100%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/package-info.java
rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/package-info.java
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
index f5751345..1ef586e6 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java
@@ -61,13 +61,13 @@ import org.springframework.context.annotation.FilterType;
"dev.caskeleton.bootstrap.autoconfigure.fileserver",
"dev.caskeleton.bootstrap.autoconfigure.httpclient",
"dev.caskeleton.bootstrap.concurrency",
- "dev.caskeleton.bootstrap.idempotency",
- "dev.caskeleton.bootstrap.outbox",
- "dev.caskeleton.bootstrap.redis",
+ "dev.caskeleton.bootstrap.autoconfigure.idempotency",
+ "dev.caskeleton.bootstrap.autoconfigure.outbox",
+ "dev.caskeleton.bootstrap.autoconfigure.redis",
"dev.caskeleton.bootstrap.runtime",
"dev.caskeleton.bootstrap.security",
"dev.caskeleton.bootstrap.settings",
- "dev.caskeleton.bootstrap.tracing",
+ "dev.caskeleton.bootstrap.autoconfigure.tracing",
"dev.caskeleton.adapter.inbound.grpc",
"dev.caskeleton.adapter.inbound.web",
"dev.caskeleton.adapter.inbound.websocket",
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java
index 9835501e..8370d3a4 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java
@@ -1,6 +1,6 @@
package dev.caskeleton.bootstrap.activation;
-import dev.caskeleton.bootstrap.autoconfigure.persistencejpa.DataSourceRequirement;
+import dev.caskeleton.bootstrap.autoconfigure.jpa.DataSourceRequirement;
import dev.caskeleton.shared.activation.MasterSwitch;
import java.util.LinkedHashMap;
import java.util.Map;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java
index d42fc3c6..a606c1e8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java
@@ -1,5 +1,7 @@
package dev.caskeleton.bootstrap.activation;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.bootstrap.runtime.startup.StartupOrder;
import java.util.List;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
@@ -44,7 +46,7 @@ public class CapabilityDependencyEnvironmentValidator implements EnvironmentPost
if (violations.isEmpty()) {
return;
}
- throw new IllegalStateException(
+ throw StartupFailures.requiredAdapterDisabled(
"This deployment enables capabilities whose dependencies are off:"
+ System.lineSeparator()
+ String.join(
@@ -53,10 +55,6 @@ public class CapabilityDependencyEnvironmentValidator implements EnvironmentPost
@Override
public int getOrder() {
- // Alongside the master-switch and profile checks: after config data has been contributed, so
- // the values read here are the ones the application will run with, and after
- // MasterSwitchEnvironmentPostProcessor has rejected legacy switch spellings — a violation
- // reported against a key the operator was told to stop using would send them somewhere useless.
- return Ordered.LOWEST_PRECEDENCE;
+ return StartupOrder.CAPABILITY_DEPENDENCY;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java
index e5cbeaae..6c089837 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.activation;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.util.List;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.env.Environment;
@@ -27,7 +28,7 @@ public class CapabilityDependencyStartupCheck implements InitializingBean {
if (violations.isEmpty()) {
return;
}
- throw new IllegalStateException(
+ throw StartupFailures.requiredAdapterDisabled(
"This deployment enables capabilities whose dependencies are off:"
+ System.lineSeparator()
+ String.join(
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java
index 6dc60510..8f59918f 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java
@@ -1,5 +1,7 @@
package dev.caskeleton.bootstrap.activation;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.bootstrap.runtime.startup.StartupOrder;
import dev.caskeleton.shared.activation.MasterSwitch;
import dev.caskeleton.shared.activation.MasterSwitchParseResult;
import dev.caskeleton.shared.activation.MasterSwitchParser;
@@ -42,14 +44,14 @@ public class MasterSwitchEnvironmentPostProcessor implements EnvironmentPostProc
private static void reject(MasterSwitchParseResult result) {
if (result instanceof MasterSwitchParseResult.Invalid invalid) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
("%s must be exactly true or false, but was \"%s\". A value this close to a boolean is a "
+ "deployment that believes it set the switch; it is rejected rather than read as "
+ "off.")
.formatted(invalid.owner().property(), invalid.raw()));
}
if (result instanceof MasterSwitchParseResult.Ambiguous ambiguous) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
("%s and %s are both set. They agree today and diverge the first time somebody edits one "
+ "of them; remove %s.")
.formatted(
@@ -58,7 +60,7 @@ public class MasterSwitchEnvironmentPostProcessor implements EnvironmentPostProc
ambiguous.legacyProperty()));
}
if (result instanceof MasterSwitchParseResult.LegacyOnly legacy) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"%s has been replaced by %s. Set the replacement; the old key is no longer read."
.formatted(legacy.legacyProperty(), legacy.replacementProperty()));
}
@@ -66,6 +68,6 @@ public class MasterSwitchEnvironmentPostProcessor implements EnvironmentPostProc
@Override
public int getOrder() {
- return Ordered.LOWEST_PRECEDENCE;
+ return StartupOrder.MASTER_SWITCH_NORMALISATION;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java
index 00206109..37f7c022 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java
@@ -1,7 +1,9 @@
package dev.caskeleton.bootstrap.activation;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.bootstrap.runtime.startup.StartupOrder;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.util.List;
-import java.util.Set;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
import org.springframework.core.Ordered;
@@ -27,11 +29,12 @@ import org.springframework.core.env.ConfigurableEnvironment;
*/
public class RuntimeEnvironmentProfileValidator implements EnvironmentPostProcessor, Ordered {
- /** The environments a deployable artifact may declare. */
- private static final Set DEPLOYABLE_ENVIRONMENTS = Set.of("local", "dev", "prod");
-
- /** Profiles that only a test-source context may activate. */
- private static final Set TEST_ONLY_ENVIRONMENTS = Set.of("test");
+ // The vocabulary used to live here, as two literal Sets. It now lives in
+ // dev.caskeleton.shared.runtime.RuntimeEnvironment, because this validator was never its only
+ // reader: seven other places asked "is this production?" and answered it themselves, three of
+ // them with a different spelling (BOOT-002). This class still owns the *rule* — exactly one
+ // environment, no default, feature flags are not profiles — while the vocabulary the rule is
+ // written against is now a single type every leaf can share.
@Override
public void postProcessEnvironment(
@@ -41,30 +44,30 @@ public class RuntimeEnvironmentProfileValidator implements EnvironmentPostProces
}
List active = List.of(environment.getActiveProfiles());
- if (active.stream().anyMatch(TEST_ONLY_ENVIRONMENTS::contains)) {
+ if (RuntimeEnvironment.isTestContext(active)) {
return;
}
if (active.isEmpty()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"SPRING_PROFILES_ACTIVE is not set. A deployable runtime declares exactly one of "
- + sortedDeployable()
+ + RuntimeEnvironment.deployableProfileNames()
+ "; there is no default, because a profile that is guessed is a deployment nobody "
+ "chose.");
}
- List environments = active.stream().filter(DEPLOYABLE_ENVIRONMENTS::contains).toList();
+ List environments = RuntimeEnvironment.activeIn(active);
if (environments.size() > 1) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"SPRING_PROFILES_ACTIVE names more than one environment "
- + environments
+ + environments.stream().map(RuntimeEnvironment::profileName).toList()
+ ". Exactly one is permitted: the safety rules of two environments cannot both apply, "
+ "and whichever loses does so silently.");
}
if (environments.isEmpty()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"SPRING_PROFILES_ACTIVE="
+ String.join(",", active)
+ " names no runtime environment. Set exactly one of "
- + sortedDeployable()
+ + RuntimeEnvironment.deployableProfileNames()
+ "; a capability is selected with its master switch, never with a profile.");
}
}
@@ -81,13 +84,8 @@ public class RuntimeEnvironmentProfileValidator implements EnvironmentPostProces
&& "dev.caskeleton.bootstrap.CaSkeletonApplication".equals(mainClass.getName());
}
- private static List sortedDeployable() {
- return DEPLOYABLE_ENVIRONMENTS.stream().sorted().toList();
- }
-
@Override
public int getOrder() {
- // Alongside the master-switch check: after config data, before anything binds a namespace.
- return Ordered.LOWEST_PRECEDENCE;
+ return StartupOrder.RUNTIME_ENVIRONMENT_PROFILE;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java
index 4fbbe27e..bd47d3d2 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java
@@ -1,12 +1,11 @@
package dev.caskeleton.bootstrap.async;
+import dev.caskeleton.bootstrap.runtime.RuntimeWarnings;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import java.util.function.DoubleSupplier;
import java.util.function.Supplier;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
/**
@@ -16,8 +15,6 @@ import org.springframework.beans.factory.ObjectProvider;
*/
public final class BackgroundJobMetrics {
- private static final Logger log = LoggerFactory.getLogger(BackgroundJobMetrics.class);
-
static final String EXECUTOR_SATURATION = "executor.saturation";
static final String EXECUTOR_REJECTED = "executor.rejected.total";
static final String JOB_RETRY = "job.retry.total";
@@ -75,7 +72,7 @@ public final class BackgroundJobMetrics {
try {
builder.get().register(registry).increment();
} catch (RuntimeException ex) {
- log.warn("background-job metrics: failed to record counter {}", meterName, ex);
+ RuntimeWarnings.meterUnavailable("background-job", meterName, ex);
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java
new file mode 100644
index 00000000..aa9e7be0
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java
@@ -0,0 +1,83 @@
+package dev.caskeleton.bootstrap.autoconfigure;
+
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import java.util.function.Predicate;
+import org.springframework.boot.context.properties.bind.BindHandler;
+import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
+import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
+import org.springframework.core.env.Environment;
+
+/**
+ * Binds a capability's settings strictly, and only when the capability is switched on.
+ *
+ *
Two properties matter and neither is available from a scanned {@code @ConfigurationProperties}
+ * class.
+ *
+ *
First, timing. A binder runs inside its master-gated auto-configuration, so a deployment that
+ * never enables a capability never binds one of its settings — a malformed {@code Duration} in a
+ * block nobody switched on cannot fail that deployment's startup.
+ *
+ *
Second, strictness. Unknown keys under the prefix are refused rather than ignored. Silently
+ * dropping {@code app.fileserver-platform.uplaod.max-file-size} is how a deployment ends up running
+ * the default limit while its configuration file says otherwise, and discovering that from a
+ * production incident is the whole failure mode this prevents.
+ *
+ *
Why this is shared
+ *
+ *
The Fileserver and HTTP client platforms each had their own copy of this — same handler, same
+ * {@code Binder.get(...).bind(PREFIX, Bindable.of(T), strict)}, same {@code orElseThrow}, differing
+ * only in which prefix they named and in one word of the failure message (BOOT-011). The one real
+ * difference between them is which property sources the unbound-element check applies to, so that
+ * is the parameter, and everything else is here once.
+ */
+public final class StrictSettingsBinder {
+
+ private StrictSettingsBinder() {}
+
+ /**
+ * Binds the prefix strictly against every property source.
+ *
+ * @param environment the environment to bind from
+ * @param prefix the settings prefix
+ * @param type the settings type
+ * @param the settings type
+ * @return the bound settings
+ */
+ public static T bind(Environment environment, String prefix, Class type) {
+ return bind(environment, prefix, type, source -> true);
+ }
+
+ /**
+ * Binds the prefix strictly, checking unbound elements only in the sources the filter accepts.
+ *
+ *
The filter exists for one real case rather than as generality for its own sake. Spring
+ * enumerates {@code APP_HTTPCLIENT_CLIENTS_0_BASE_URL} from the system environment as {@code
+ * ...clients[0].base.url} while binding it as {@code ...clients[0].base-url}, so leaving the
+ * system environment to {@link NoUnboundElementsBindHandler} would reject every correctly spelled
+ * variable and still miss the misspelled ones. A caller that excludes it here is expected to
+ * check that source by another route.
+ *
+ * @param environment the environment to bind from
+ * @param prefix the settings prefix
+ * @param type the settings type
+ * @param unboundCheckScope which property sources the unbound-element check applies to
+ * @param the settings type
+ * @return the bound settings
+ */
+ public static T bind(
+ Environment environment,
+ String prefix,
+ Class type,
+ Predicate unboundCheckScope) {
+ BindHandler strict =
+ new NoUnboundElementsBindHandler(BindHandler.DEFAULT, unboundCheckScope::test);
+ return Binder.get(environment)
+ .bind(prefix, Bindable.of(type), strict)
+ .orElseThrow(
+ () ->
+ StartupFailures.envValidation(
+ prefix + " could not be bound although the capability is enabled"));
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java
index 65e9140d..c0ad290b 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java
@@ -17,6 +17,7 @@ import dev.caskeleton.adapter.inbound.web.fileserver.security.FileserverRequestC
import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusChecksumVerifier;
import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusProperties;
import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusRequestParser;
+import dev.caskeleton.adapter.outbound.fileserver.platform.observability.MicrometerFileserverMetrics;
import dev.caskeleton.application.fileserver.api.StorageNamespace;
import dev.caskeleton.application.fileserver.api.transfer.ConditionalRequestEvaluator;
import dev.caskeleton.application.fileserver.api.transfer.ContentDispositionFactory;
@@ -28,6 +29,7 @@ import dev.caskeleton.application.fileserver.download.DownloadPolicy;
import dev.caskeleton.application.fileserver.observability.FileserverMetricsPort;
import dev.caskeleton.application.fileserver.observability.SafeFileFingerprint;
import dev.caskeleton.application.fileserver.upload.UploadPolicy;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import io.micrometer.core.instrument.MeterRegistry;
import java.nio.charset.StandardCharsets;
import java.util.List;
@@ -297,7 +299,7 @@ public class FileserverPlatformAutoConfiguration {
SafeFileFingerprint fileserverFingerprint(FileserverPlatformSettings properties) {
String key = properties.observability().fingerprintKey();
if (key == null || key.isBlank()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
FileserverPlatformSettings.PREFIX
+ ".observability.fingerprint-key must be configured while metrics are enabled: an "
+ "unkeyed digest of an enumerable identifier is reversible");
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java
index 2d495cdb..c53c9715 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.autoconfigure.fileserver;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.nio.file.Path;
import java.time.Duration;
import java.util.List;
@@ -77,7 +78,7 @@ public record FileserverPlatformSettings(
requireText(publishMode, "storage.publish-mode");
long bytes = requireSize(bufferSize, "storage.buffer-size");
if (bytes < MINIMUM_BUFFER_BYTES || bytes > MAXIMUM_BUFFER_BYTES) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX + ".storage.buffer-size must be between 4KB and 8MB");
}
}
@@ -99,13 +100,13 @@ public record FileserverPlatformSettings(
long maxRequest = requireSize(maxRequestSize, "upload.max-request-size");
long reservation = requireSize(initialReservation, "upload.initial-reservation");
if (maxRequest < maxFile) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX
+ ".upload.max-request-size must be at least upload.max-file-size: a request "
+ "envelope smaller than the file it carries rejects every maximum-sized upload");
}
if (reservation > maxFile) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX + ".upload.initial-reservation must not exceed upload.max-file-size");
}
requirePositive(maxParts, "upload.max-parts");
@@ -152,7 +153,7 @@ public record FileserverPlatformSettings(
requirePositive(queueCapacity, "transfer.queue-capacity");
requirePositive(awaitSeconds, "transfer.await-seconds");
if (coreSize > maxSize) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX + ".transfer.core-size must not exceed transfer.max-size");
}
}
@@ -235,11 +236,11 @@ public record FileserverPlatformSettings(
requireFraction(softHighWater, "quota.soft-high-water");
requireFraction(hardHighWater, "quota.hard-high-water");
if (softHighWater >= hardHighWater) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX + ".quota.soft-high-water must be below quota.hard-high-water");
}
if (scopeUploadPermits > instanceUploadPermits) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX + ".quota.scope-upload-permits must not exceed quota.instance-upload-permits");
}
}
@@ -298,13 +299,13 @@ public record FileserverPlatformSettings(
private static void requireText(String value, String name) {
if (value == null || value.isBlank()) {
- throw new IllegalStateException(PREFIX + "." + name + " must be non-blank");
+ throw StartupFailures.envValidation(PREFIX + "." + name + " must be non-blank");
}
}
private static void requireRoles(List roles, String name) {
if (roles == null || roles.isEmpty()) {
- throw new IllegalStateException(PREFIX + "." + name + " must list at least one role");
+ throw StartupFailures.envValidation(PREFIX + "." + name + " must list at least one role");
}
for (String role : roles) {
requireText(role, name);
@@ -313,19 +314,19 @@ public record FileserverPlatformSettings(
private static void requirePositive(int value, String name) {
if (value < 1) {
- throw new IllegalStateException(PREFIX + "." + name + " must be positive");
+ throw StartupFailures.envValidation(PREFIX + "." + name + " must be positive");
}
}
private static void requirePositive(Duration value, String name) {
if (value == null || value.isNegative() || value.isZero()) {
- throw new IllegalStateException(PREFIX + "." + name + " must be a positive duration");
+ throw StartupFailures.envValidation(PREFIX + "." + name + " must be a positive duration");
}
}
private static void requireFraction(double value, String name) {
if (!(value > 0) || value > 1) {
- throw new IllegalStateException(PREFIX + "." + name + " must be within (0, 1]");
+ throw StartupFailures.envValidation(PREFIX + "." + name + " must be within (0, 1]");
}
}
@@ -337,14 +338,14 @@ public record FileserverPlatformSettings(
*/
private static long requireSize(DataSize value, String name) {
if (value == null || value.toBytes() < 1) {
- throw new IllegalStateException(PREFIX + "." + name + " must be a positive size");
+ throw StartupFailures.envValidation(PREFIX + "." + name + " must be a positive size");
}
return value.toBytes();
}
private static void requireAbsolute(Path root) {
if (root == null || !root.isAbsolute()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
PREFIX
+ ".storage.root must be an absolute path: a relative root resolves against the "
+ "process working directory, which differs between a container and a test");
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java
index 0b1df654..599e24fb 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java
@@ -1,41 +1,24 @@
package dev.caskeleton.bootstrap.autoconfigure.fileserver;
-import org.springframework.boot.context.properties.bind.BindHandler;
-import org.springframework.boot.context.properties.bind.Bindable;
-import org.springframework.boot.context.properties.bind.Binder;
-import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
+import dev.caskeleton.bootstrap.autoconfigure.StrictSettingsBinder;
import org.springframework.core.env.Environment;
/**
* Binds {@link FileserverPlatformSettings} strictly, and only when asked.
*
- *
Two properties matter here and neither is available from a scanned
- * {@code @ConfigurationProperties} class.
- *
- *
First, timing. This runs inside the master-gated auto-configuration, so a deployment that
- * never enables the Fileserver never binds a Fileserver setting — a malformed {@code Duration} in a
- * block nobody switched on cannot fail its startup.
- *
- *
Second, strictness. Unknown keys under the prefix are refused rather than ignored. Silently
- * dropping {@code app.fileserver-platform.uplaod.max-file-size} is how a deployment ends up running
- * the default limit while its configuration file says otherwise, and a file capability is exactly
- * the wrong place to discover that from a production incident.
+ *
The mechanics — master-gated timing, refusing unknown keys under the prefix — live in {@link
+ * StrictSettingsBinder}, which this and the HTTP client platform's binder used to hold a copy of
+ * each (BOOT-011). What is left here is the one thing that is actually specific: which settings
+ * type is bound. Every property source is in scope for the unbound-element check, because this
+ * prefix is populated from YAML placeholders rather than bound directly from the system
+ * environment.
*/
final class FileserverPlatformSettingsBinder {
private FileserverPlatformSettingsBinder() {}
static FileserverPlatformSettings bind(Environment environment) {
- BindHandler strict = new NoUnboundElementsBindHandler(BindHandler.DEFAULT);
- return Binder.get(environment)
- .bind(
- FileserverPlatformSettings.PREFIX,
- Bindable.of(FileserverPlatformSettings.class),
- strict)
- .orElseThrow(
- () ->
- new IllegalStateException(
- FileserverPlatformSettings.PREFIX
- + " could not be bound although the capability is enabled"));
+ return StrictSettingsBinder.bind(
+ environment, FileserverPlatformSettings.PREFIX, FileserverPlatformSettings.class);
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java
index 9bbb9cb0..bcc76234 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java
@@ -7,6 +7,8 @@ import dev.caskeleton.adapter.outbound.fileserver.platform.security.UnenforcedFi
import dev.caskeleton.application.fileserver.admin.AdminAuditPort;
import dev.caskeleton.application.fileserver.api.security.FileAccessPolicy;
import dev.caskeleton.application.fileserver.observability.FileserverAuditPort;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
@@ -34,8 +36,6 @@ public class FileserverSecurityConfiguration {
private static final String MODE_ROLE_BASED = "role-based";
private static final String MODE_UNENFORCED = "unenforced";
- private static final Set PRODUCTION_PROFILES = Set.of("prod", "production");
-
@Bean
@ConditionalOnMissingBean
FileAccessPolicy fileserverAccessPolicy(
@@ -55,11 +55,11 @@ public class FileserverSecurityConfiguration {
false);
case MODE_UNENFORCED -> unenforcedOrFail(environment);
case MODE_REQUIRED ->
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"fileserver is enabled without an authorization policy: supply a FileAccessPolicy "
+ "bean, or set app.fileserver-platform.security.access-policy to role-based");
default ->
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"app.fileserver-platform.security.access-policy must be one of required, role-based, "
+ "unenforced");
};
@@ -78,14 +78,12 @@ public class FileserverSecurityConfiguration {
}
private static FileAccessPolicy unenforcedOrFail(Environment environment) {
- for (String profile : environment.getActiveProfiles()) {
- if (PRODUCTION_PROFILES.contains(profile.trim().toLowerCase(Locale.ROOT))) {
- throw new IllegalStateException(
- "app.fileserver-platform.security.access-policy=unenforced authorizes every file "
- + "operation and must not be active under the '"
- + profile
- + "' profile");
- }
+ if (RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()))) {
+ throw StartupFailures.profileMismatch(
+ "app.fileserver-platform.security.access-policy=unenforced authorizes every file "
+ + "operation and must not be active under the '"
+ + RuntimeEnvironment.PROD.profileName()
+ + "' profile");
}
return new UnenforcedFileAccessPolicy();
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java
index b974e8e5..d93e62e8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java
@@ -7,8 +7,8 @@ import dev.caskeleton.adapter.outbound.fileserver.platform.security.UnenforcedFi
import dev.caskeleton.application.fileserver.api.metadata.FileMetadataStore;
import dev.caskeleton.application.fileserver.api.security.FileAccessPolicy;
import dev.caskeleton.application.fileserver.upload.FileVerificationService;
-import java.util.Locale;
-import java.util.Set;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
+import java.util.List;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -30,8 +30,6 @@ import org.springframework.core.env.Environment;
@Configuration(proxyBeanMethods = false)
public class FileserverStartupConfiguration {
- private static final Set PRODUCTION_PROFILES = Set.of("prod", "production");
-
@Bean
@ConditionalOnMissingBean
FileserverStartupValidator fileserverStartupValidator() {
@@ -88,11 +86,6 @@ public class FileserverStartupConfiguration {
}
private static boolean isProductionProfile(Environment environment) {
- for (String profile : environment.getActiveProfiles()) {
- if (PRODUCTION_PROFILES.contains(profile.trim().toLowerCase(Locale.ROOT))) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java
index 9e58e350..3ce92afb 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java
@@ -2,9 +2,9 @@ package dev.caskeleton.bootstrap.autoconfigure.fileserver;
import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalStorageProbeResult;
import dev.caskeleton.application.fileserver.api.content.PublishMode;
+import dev.caskeleton.bootstrap.runtime.startup.StartupPhase;
+import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext;
import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
/**
* Fail-closed startup gate for the Fileserver platform.
@@ -16,6 +16,9 @@ import java.util.List;
*/
public final class FileserverStartupValidator {
+ /** Shared lead-in for both entry points; the collected violations follow it. */
+ private static final String SUMMARY = "fileserver startup validation failed";
+
/**
* Validates the publish mode against what the storage probe actually proved.
*
@@ -23,9 +26,10 @@ public final class FileserverStartupValidator {
* allowed to fall back to a metadata pointer publish, so it only requires the mandatory checks.
*/
public void validate(PublishMode publishMode, LocalStorageProbeResult probeResult) {
- List violations = new ArrayList<>();
+ StartupValidationContext violations =
+ StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION);
collectStorageViolations(publishMode, probeResult, violations);
- failOn(violations);
+ violations.failIfViolated(SUMMARY);
}
/**
@@ -35,47 +39,51 @@ public final class FileserverStartupValidator {
* rather than discovering them one restart at a time.
*/
public void validate(FileserverStartupCheck check) {
- List violations = new ArrayList<>();
+ StartupValidationContext violations =
+ StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION);
collectStorageViolations(check.publishMode(), check.probeResult(), violations);
collectRootViolations(check, violations);
if (check.multiInstance() && !check.metadataStorePresent()) {
- violations.add("multi-instance mode requires a shared metadata store");
+ violations.reject("multi-instance mode requires a shared metadata store");
}
if (check.productionProfile() && check.allowAllAccessPolicy()) {
- violations.add(
+ violations.reject(
"a no-op allow-all FileAccessPolicy must not be active in a production profile");
}
if (check.scannerRequired() && !check.scannerPresent()) {
- violations.add("scanner-required is set but no file verifier bean is registered");
+ violations.reject("scanner-required is set but no file verifier bean is registered");
}
if (check.nginxDelegationEnabled() && !check.nginxInternalMappingValidated()) {
- violations.add("nginx delegation is enabled without a validated internal URI mapping");
+ violations.reject("nginx delegation is enabled without a validated internal URI mapping");
}
- failOn(violations);
+ violations.failIfViolated(SUMMARY);
}
private void collectStorageViolations(
- PublishMode publishMode, LocalStorageProbeResult probeResult, List violations) {
+ PublishMode publishMode,
+ LocalStorageProbeResult probeResult,
+ StartupValidationContext violations) {
if (!probeResult.writableRoot()) {
- violations.add("storage root is not writable");
+ violations.reject("storage root is not writable");
}
if (!probeResult.atomicCreate()) {
- violations.add("storage root does not provide atomic create");
+ violations.reject("storage root does not provide atomic create");
}
if (!probeResult.sameFileStore()) {
- violations.add("staging, content, and quarantine are not on the same file store");
+ violations.reject("staging, content, and quarantine are not on the same file store");
}
if (!probeResult.symlinkNoFollow()) {
- violations.add("storage root does not refuse to follow symbolic links");
+ violations.reject("storage root does not refuse to follow symbolic links");
}
if (!probeResult.descriptorRelativeAccess()) {
- violations.add(
+ violations.reject(
"storage root does not support descriptor-relative access, so every open would "
+ "re-resolve a pathname and leave the parent-replacement race open");
}
if (publishMode == PublishMode.ATOMIC_MOVE_REQUIRED && !probeResult.atomicMove()) {
- violations.add("publish mode requires an atomic move that the storage probe could not prove");
+ violations.reject(
+ "publish mode requires an atomic move that the storage probe could not prove");
}
}
@@ -85,21 +93,15 @@ public final class FileserverStartupValidator {
*
Storing uploads under a web root or a config root is what turns an upload into remote code
* execution, so this is checked before anything is written.
*/
- private void collectRootViolations(FileserverStartupCheck check, List violations) {
+ private void collectRootViolations(
+ FileserverStartupCheck check, StartupValidationContext violations) {
Path root = check.storageRoot().toAbsolutePath().normalize();
for (Path forbidden : check.forbiddenRootAncestors()) {
Path normalized = forbidden.toAbsolutePath().normalize();
if (root.startsWith(normalized) || normalized.startsWith(root)) {
- violations.add("storage root overlaps a web root or configuration root");
+ violations.reject("storage root overlaps a web root or configuration root");
return;
}
}
}
-
- private static void failOn(List violations) {
- if (!violations.isEmpty()) {
- throw new IllegalStateException(
- "fileserver startup validation failed: " + String.join("; ", violations));
- }
- }
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java
index a40d1212..fd947c93 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java
@@ -43,6 +43,7 @@ import dev.caskeleton.application.fileserver.recovery.StagingUploadLocator;
import dev.caskeleton.application.fileserver.upload.FileVerificationService;
import dev.caskeleton.application.fileserver.upload.UploadContentGateway;
import dev.caskeleton.application.fileserver.upload.UploadStorageGateway;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.nio.file.Path;
import java.time.Clock;
import java.util.List;
@@ -112,7 +113,7 @@ public class FileserverStorageConfiguration {
LocalStorageProbeResult fileserverStorageProbeResult(LocalStorageProperties properties) {
LocalStorageProbeResult result = new LocalStorageCapabilityProbe(properties).run();
if (!result.mandatoryChecksPassed()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"fileserver storage root failed a mandatory capability probe: "
+ result.failures()
+ "; the capability refuses to start rather than publish files it cannot place "
@@ -264,7 +265,7 @@ public class FileserverStorageConfiguration {
for (Path forbidden : storage.forbiddenRootAncestors()) {
Path ancestor = forbidden.toAbsolutePath().normalize();
if (root.startsWith(ancestor)) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"fileserver storage root must not live under a configured forbidden ancestor");
}
}
@@ -275,7 +276,7 @@ public class FileserverStorageConfiguration {
try {
return PublishMode.valueOf(canonical);
} catch (IllegalArgumentException unknown) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
FileserverPlatformSettings.PREFIX
+ ".storage.publish-mode must be one of atomic-move-required, "
+ "atomic-move-preferred, metadata-pointer",
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java
index 066d68ac..3ab25afd 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java
@@ -8,6 +8,7 @@ import dev.caskeleton.adapter.outbound.httpclient.auth.OAuth2CredentialProvider;
import dev.caskeleton.adapter.outbound.httpclient.auth.ReactiveCredentialProviderRegistry;
import dev.caskeleton.adapter.outbound.httpclient.auth.ReactiveRequestCredentialProvider;
import dev.caskeleton.adapter.outbound.httpclient.auth.StaticBearerCredentialProvider;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.time.Clock;
import java.util.Set;
import java.util.function.Function;
@@ -36,7 +37,7 @@ public class HttpClientAuthenticationAutoConfiguration {
@ConditionalOnMissingBean(name = "httpClientSecretResolver")
Function httpClientSecretResolver() {
return reference -> {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"no http client secret resolver is configured for reference '" + reference + "'");
};
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java
index 328e9ef8..c554e4c3 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.autoconfigure.httpclient;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.RecordComponent;
import java.util.LinkedHashMap;
@@ -84,7 +85,7 @@ final class HttpClientEnvironmentKeys {
}
}
if (!unrecognised.isEmpty()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"unrecognised HTTP client environment variables "
+ unrecognised
+ "; see docs/httpclient/env-fields.yaml for the accepted names");
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java
index 46e4298d..79cff694 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.autoconfigure.httpclient;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.time.Duration;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -51,7 +52,7 @@ public record HttpClientPlatformSettings(
// An active platform with nothing to call still holds transport providers, a resilience
// registry and five caller-facing gateways that no caller can reach. That is not a working
// deployment with an empty configuration; it is a configuration mistake with a running cost.
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"HTTPCLIENT_ACTIVE_WITHOUT_CLIENTS: "
+ PREFIX
+ ".enabled is true but no client is declared under "
@@ -76,16 +77,16 @@ public record HttpClientPlatformSettings(
Map byNormalisedForm = new LinkedHashMap<>();
for (String name : names) {
if (name == null || name.isBlank()) {
- throw new IllegalStateException(where + "[*].name must be non-blank");
+ throw StartupFailures.envValidation(where + "[*].name must be non-blank");
}
if (seen.contains(name)) {
- throw new IllegalStateException(where + " declares '" + name + "' more than once");
+ throw StartupFailures.envValidation(where + " declares '" + name + "' more than once");
}
seen.add(name);
String normalised = name.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]", "");
String previous = byNormalisedForm.putIfAbsent(normalised, name);
if (previous != null) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
where
+ " declares '"
+ previous
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java
index efbf5c61..1ea95d3c 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java
@@ -1,34 +1,22 @@
package dev.caskeleton.bootstrap.autoconfigure.httpclient;
-import org.springframework.boot.context.properties.bind.BindHandler;
-import org.springframework.boot.context.properties.bind.Bindable;
-import org.springframework.boot.context.properties.bind.Binder;
-import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
+import dev.caskeleton.bootstrap.autoconfigure.StrictSettingsBinder;
import org.springframework.core.env.Environment;
import org.springframework.core.env.SystemEnvironmentPropertySource;
/**
* Binds {@link HttpClientPlatformSettings} strictly, and only when asked.
*
- *
Two properties matter here and neither is available from a scanned
- * {@code @ConfigurationProperties} class.
+ *
The mechanics live in {@link StrictSettingsBinder} (BOOT-011). What stays here is this
+ * platform's one genuine difference: strictness needs two mechanisms because one does not cover
+ * both surfaces.
*
- *
First, timing. This runs inside the master-gated auto-configuration, so a deployment that
- * never enables the platform never binds one of its settings — a malformed {@code Duration} in a
- * block nobody switched on cannot fail its startup.
- *
- *
Second, strictness. Unknown keys under the prefix are refused rather than ignored. Silently
- * dropping {@code app.httpclient.clients[0].timeuot.total-call} leaves the client running the
- * default four-second budget while the configuration file says otherwise, which is exactly the kind
- * of divergence an outbound call platform should never make an operator discover from an incident.
- *
- *
Strictness is delivered by two mechanisms because one does not cover both surfaces. Property
- * sources that enumerate their keys in property form — YAML, properties files, test overrides — are
- * checked by {@link NoUnboundElementsBindHandler}. The system environment is excluded from that
- * handler and checked by {@link HttpClientEnvironmentKeys#rejectUnrecognised} instead: Spring
- * enumerates {@code APP_HTTPCLIENT_CLIENTS_0_BASE_URL} as {@code ...clients[0].base.url} while
- * binding it as {@code ...clients[0].base-url}, so leaving it to the handler would reject every
- * correctly spelled variable and still miss the misspelled ones.
+ *
Property sources that enumerate their keys in property form — YAML, properties files, test
+ * overrides — are checked by the shared binder's unbound-element handler. The system environment is
+ * excluded from it and checked by {@link HttpClientEnvironmentKeys#rejectUnrecognised} instead:
+ * Spring enumerates {@code APP_HTTPCLIENT_CLIENTS_0_BASE_URL} as {@code ...clients[0].base.url}
+ * while binding it as {@code ...clients[0].base-url}, so leaving it to the handler would reject
+ * every correctly spelled variable and still miss the misspelled ones.
*/
final class HttpClientPlatformSettingsBinder {
@@ -36,19 +24,10 @@ final class HttpClientPlatformSettingsBinder {
static HttpClientPlatformSettings bind(Environment environment) {
HttpClientEnvironmentKeys.rejectUnrecognised(environment);
- BindHandler strict =
- new NoUnboundElementsBindHandler(
- BindHandler.DEFAULT,
- source -> !(source.getUnderlyingSource() instanceof SystemEnvironmentPropertySource));
- return Binder.get(environment)
- .bind(
- HttpClientPlatformSettings.PREFIX,
- Bindable.of(HttpClientPlatformSettings.class),
- strict)
- .orElseThrow(
- () ->
- new IllegalStateException(
- HttpClientPlatformSettings.PREFIX
- + " could not be bound although the platform is enabled"));
+ return StrictSettingsBinder.bind(
+ environment,
+ HttpClientPlatformSettings.PREFIX,
+ HttpClientPlatformSettings.class,
+ source -> !(source.getUnderlyingSource() instanceof SystemEnvironmentPropertySource));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java
index b85dff42..3b9856d8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java
@@ -19,6 +19,7 @@ import dev.caskeleton.adapter.outbound.httpclient.transport.ReactiveTransportPro
import dev.caskeleton.adapter.outbound.httpclient.transport.TransportLifecycleListener;
import dev.caskeleton.adapter.outbound.httpclient.webclient.WebClientRuntimeFactory;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.random.RandomGenerator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -96,12 +97,21 @@ public class HttpClientProfileAutoConfiguration {
return new ClientRuntimeRegistry(runtimes);
}
+ /**
+ * Maps the active profiles onto the HTTP client platform's own production/non-production split.
+ *
+ *
Two types named {@code RuntimeEnvironment} meet here and they are different concepts, so the
+ * shared one is spelled out in full rather than imported. {@code
+ * dev.caskeleton.shared.runtime.RuntimeEnvironment} is the repository-wide profile vocabulary —
+ * the single answer to "which environment is this?" (BOOT-002). The imported {@code
+ * ...httpclient.profile.RuntimeEnvironment} is this adapter's binary hardening switch. Deriving
+ * the second from the first is the point: the adapter keeps its own vocabulary while the question
+ * "is this production?" is answered in exactly one place.
+ */
private RuntimeEnvironment runtimeEnvironment(Environment environment) {
- for (String profile : environment.getActiveProfiles()) {
- if ("prod".equalsIgnoreCase(profile) || "production".equalsIgnoreCase(profile)) {
- return RuntimeEnvironment.PRODUCTION;
- }
- }
- return RuntimeEnvironment.NON_PRODUCTION;
+ return dev.caskeleton.shared.runtime.RuntimeEnvironment.isProductionActive(
+ List.of(environment.getActiveProfiles()))
+ ? RuntimeEnvironment.PRODUCTION
+ : RuntimeEnvironment.NON_PRODUCTION;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java
new file mode 100644
index 00000000..3e94322b
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java
@@ -0,0 +1,39 @@
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
+
+/**
+ * The bean shape a selected idempotency provider requires the composition root to hold.
+ *
+ *
This used to be a {@code switch} in {@code IdempotencyProviderSelectionConfig} — so adding a
+ * provider meant editing two places, the enum and the switch, and forgetting the second still
+ * compiled (BOOT-010). Now the requirement is a constructor argument of the enum constant, which
+ * means it cannot be added without being stated.
+ *
+ *
It stays here rather than being contributed by the provider modules. A count of beans in the
+ * composition root is assembly knowledge: the Redis adapter has no view of how many stores this
+ * deployment ended up holding, and asking it to declare one would give an adapter an opinion about
+ * a graph it is only a part of.
+ *
+ * @param jdbcStores V1 {@code IdempotencyStorePort} beans expected
+ * @param jdbcExecutors V1 {@code IdempotencyExecutor} beans expected
+ * @param ownerSafeStores V2 {@code IdempotencyStorePortV2} beans expected
+ * @param ownerSafeExecutors V2 {@code IdempotencyExecutorV2} beans expected
+ */
+record IdempotencyComposition(
+ int jdbcStores, int jdbcExecutors, int ownerSafeStores, int ownerSafeExecutors) {
+
+ /**
+ * Redis and PostgreSQL declare the same shape because they satisfy one contract: the count is by
+ * type, so the guard cannot tell them apart and does not need to. What it refuses is a deployment
+ * holding two stores, or a store with nothing to drive it, whichever provider produced them.
+ */
+ String describe() {
+ return jdbcStores
+ + " JDBC V1 store(s), "
+ + jdbcExecutors
+ + " JDBC V1 executor(s), "
+ + ownerSafeStores
+ + " owner-safe V2 store(s) and "
+ + ownerSafeExecutors
+ + " owner-safe V2 executor(s)";
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyConfig.java
similarity index 94%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyConfig.java
index c3bdc4dc..018dcb92 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import dev.caskeleton.application.idempotency.IdempotencyExecutor;
import dev.caskeleton.application.idempotency.IdempotencyStorePort;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSelectionConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSelectionConfig.java
similarity index 74%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSelectionConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSelectionConfig.java
index c6209926..58c8ca94 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSelectionConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSelectionConfig.java
@@ -1,9 +1,10 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import dev.caskeleton.application.idempotency.IdempotencyExecutor;
import dev.caskeleton.application.idempotency.IdempotencyStorePort;
import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2;
import dev.caskeleton.application.idempotency.v2.IdempotencyStorePortV2;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -44,20 +45,13 @@ public class IdempotencyProviderSelectionConfig {
ObjectProvider ownerSafeStores,
ObjectProvider ownerSafeExecutors) {
return () -> {
- Counts found =
- new Counts(
+ IdempotencyComposition found =
+ new IdempotencyComposition(
count(jdbcStores),
count(jdbcExecutors),
count(ownerSafeStores),
count(ownerSafeExecutors));
- switch (settings.provider()) {
- case DISABLED -> require(found, new Counts(0, 0, 0, 0));
- case JDBC -> require(found, new Counts(1, 1, 0, 0));
- case REDIS, POSTGRESQL -> require(found, new Counts(0, 0, 1, 1));
- default ->
- throw new IllegalStateException(
- "Unsupported idempotency provider: " + settings.provider());
- }
+ require(found, settings.provider().composition());
};
}
@@ -65,29 +59,13 @@ public class IdempotencyProviderSelectionConfig {
return Math.toIntExact(beans.stream().count());
}
- private static void require(Counts found, Counts expected) {
+ private static void require(IdempotencyComposition found, IdempotencyComposition expected) {
if (!found.equals(expected)) {
- throw new IllegalStateException(
+ throw StartupFailures.requiredAdapterDisabled(
"Idempotency provider selection is ambiguous or incomplete: expected "
+ expected.describe()
+ ", but found "
+ found.describe());
}
}
-
- /** The four bean counts the selection is exact about, in one comparable value. */
- private record Counts(
- int jdbcStores, int jdbcExecutors, int ownerSafeStores, int ownerSafeExecutors) {
-
- String describe() {
- return jdbcStores
- + " JDBC V1 store(s), "
- + jdbcExecutors
- + " JDBC V1 executor(s), "
- + ownerSafeStores
- + " owner-safe V2 store(s) and "
- + ownerSafeExecutors
- + " owner-safe V2 executor(s)";
- }
- }
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java
new file mode 100644
index 00000000..c77da6f5
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java
@@ -0,0 +1,67 @@
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.bind.ConstructorBinding;
+
+/** Exact provider selector; provider precedence is never inferred from the classpath. */
+@ConfigurationProperties(prefix = "ca-skeleton.capabilities.idempotency")
+public record IdempotencyProviderSettings(Provider provider) {
+
+ @ConstructorBinding
+ public IdempotencyProviderSettings {
+ provider = provider == null ? Provider.JDBC : provider;
+ }
+
+ /**
+ * The stores a deployment may select.
+ *
+ *
{@code POSTGRESQL} selects the owner-safe V2 store in the JPA leaf. It had no value here for
+ * as long as it existed, so the only way to reach it was to construct it in a test: a capability
+ * with an implementation, a schema stream and an integration suite that no deployment could turn
+ * on. A store nobody can select is not a feature, and leaving the selector out did not make the
+ * code any less present in the artifact.
+ */
+ public enum Provider {
+
+ /** Nothing composed — the capability is off. */
+ DISABLED(0, 0, 0, 0),
+
+ /** The JDBC V1 store and its executor. */
+ JDBC(1, 1, 0, 0),
+
+ /** The owner-safe V2 store and its executor. */
+ REDIS(0, 0, 1, 1),
+
+ /** The owner-safe V2 store and its executor — the same contract Redis satisfies. */
+ POSTGRESQL(0, 0, 1, 1);
+
+ private final int jdbcStores;
+ private final int jdbcExecutors;
+ private final int ownerSafeStores;
+ private final int ownerSafeExecutors;
+
+ Provider(int jdbcStores, int jdbcExecutors, int ownerSafeStores, int ownerSafeExecutors) {
+ this.jdbcStores = jdbcStores;
+ this.jdbcExecutors = jdbcExecutors;
+ this.ownerSafeStores = ownerSafeStores;
+ this.ownerSafeExecutors = ownerSafeExecutors;
+ }
+
+ /**
+ * The bean shape this provider requires the composition root to hold.
+ *
+ *
Carried by the constant rather than decided by a {@code switch} elsewhere, so a fifth
+ * provider cannot be added without saying what it needs — the compiler asks for the four
+ * arguments (BOOT-010).
+ *
+ *
Held as four ints rather than as a shared {@link IdempotencyComposition} constant because
+ * Error Prone's {@code ImmutableEnumChecker} refuses a reference field in an enum unless its
+ * type carries {@code @Immutable}, and that annotation is not a dependency of this build.
+ * Adding one to satisfy a checker would be a worse trade than four primitives.
+ */
+ IdempotencyComposition composition() {
+ return new IdempotencyComposition(
+ jdbcStores, jdbcExecutors, ownerSafeStores, ownerSafeExecutors);
+ }
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettings.java
similarity index 95%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettings.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettings.java
index 31cdd071..fcc771d7 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettings.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.time.Duration;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/PostgreSqlIdempotencyProviderConfig.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/PostgreSqlIdempotencyProviderConfig.java
index e9f35eb4..1ba12fdc 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/PostgreSqlIdempotencyProviderConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import dev.caskeleton.adapter.outbound.persistence.postgresql.idempotency.PostgreSqlOwnerSafeIdempotencyStore;
import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DataSourceRequirement.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DataSourceRequirement.java
index 71537d32..bf7d81af 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DataSourceRequirement.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
+package dev.caskeleton.bootstrap.autoconfigure.jpa;
import dev.caskeleton.shared.activation.MasterSwitch;
import java.util.ArrayList;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/DatabaseReadinessGroupPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DatabaseReadinessGroupPostProcessor.java
similarity index 61%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/DatabaseReadinessGroupPostProcessor.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DatabaseReadinessGroupPostProcessor.java
index ee0ddc48..977e972b 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/DatabaseReadinessGroupPostProcessor.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DatabaseReadinessGroupPostProcessor.java
@@ -1,16 +1,11 @@
-package dev.caskeleton.bootstrap.runtime;
+package dev.caskeleton.bootstrap.autoconfigure.jpa;
-import dev.caskeleton.bootstrap.autoconfigure.persistencejpa.DataSourceRequirement;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
+import dev.caskeleton.bootstrap.runtime.ReadinessGroup;
+import dev.caskeleton.bootstrap.runtime.startup.StartupOrder;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
-import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
/**
* Adds the database readiness contributor to the readiness group, and only when it will exist.
@@ -40,32 +35,13 @@ public class DatabaseReadinessGroupPostProcessor implements EnvironmentPostProce
if (!DataSourceRequirement.isRequired(environment)) {
return;
}
- String configured =
- environment.getProperty(RedisReadinessGroupPostProcessor.READINESS_INCLUDE, "");
- Set members = new LinkedHashSet<>();
- Arrays.stream(configured.split(","))
- .map(String::trim)
- .filter(member -> !member.isEmpty())
- .forEach(members::add);
- if (!members.add(DB_CONTRIBUTOR)) {
- // Already named explicitly. Repeating it would be harmless and would also hide that the
- // operator asked for it themselves.
- return;
- }
- environment
- .getPropertySources()
- .addFirst(
- new MapPropertySource(
- PROPERTY_SOURCE,
- Map.of(
- RedisReadinessGroupPostProcessor.READINESS_INCLUDE,
- String.join(",", members))));
+ ReadinessGroup.include(environment, PROPERTY_SOURCE, DB_CONTRIBUTOR);
}
@Override
public int getOrder() {
// After config data, for the same reason the Redis one does; and after the Redis processor, so
// the two appends chain deterministically instead of racing for the same property.
- return ConfigDataEnvironmentPostProcessor.ORDER + 2;
+ return StartupOrder.READINESS_DATABASE;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java
index d3b18bea..6d60e038 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java
@@ -1,6 +1,7 @@
package dev.caskeleton.bootstrap.autoconfigure.jpa;
import dev.caskeleton.adapter.outbound.persistence.migration.FlywaySchemaPolicy;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.util.Arrays;
import java.util.Objects;
import org.springframework.core.env.Environment;
@@ -48,7 +49,7 @@ public final class JpaDangerousConfigurationGuard {
if (Boolean.TRUE.equals(environment.getProperty(OPEN_IN_VIEW_KEY, Boolean.class))
&& !localConvenience) {
- throw new IllegalStateException(
+ throw StartupFailures.profileMismatch(
OPEN_IN_VIEW_KEY
+ " must be false outside a local convenience profile: it holds the persistence"
+ " context open through view rendering, so a lazy association touched by the"
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java
index 16b80ecd..10ecbfe8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.autoconfigure.jpa;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Objects;
@@ -57,7 +58,7 @@ public final class JpaDataSourceProfileValidator {
try (Connection connection = dataSource.getConnection()) {
versionPolicy.requireStable(connection.getMetaData());
} catch (SQLException unreachable) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"could not open a connection to validate the datasource: "
+ unreachable.getMessage()
+ ". A context that starts without this check reports healthy and fails on whoever "
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaOffAutoConfigurationImportFilter.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaOffAutoConfigurationImportFilter.java
index c5e31883..e21cfb50 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaOffAutoConfigurationImportFilter.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
+package dev.caskeleton.bootstrap.autoconfigure.jpa;
import java.util.Set;
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PersistenceJpaRootAutoConfiguration.java
similarity index 95%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PersistenceJpaRootAutoConfiguration.java
index dc8bfd30..66ac96aa 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PersistenceJpaRootAutoConfiguration.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
+package dev.caskeleton.bootstrap.autoconfigure.jpa;
import dev.caskeleton.adapter.outbound.persistence.config.JpaAdapterComponentsConfig;
import dev.caskeleton.adapter.outbound.persistence.config.PersistenceVendorSettings;
@@ -6,9 +6,6 @@ import dev.caskeleton.adapter.outbound.persistence.fileserver.FileserverJpaPersi
import dev.caskeleton.adapter.outbound.persistence.h2.H2PersistenceConfig;
import dev.caskeleton.adapter.outbound.persistence.notification.configuration.NotificationJpaPersistenceFacade;
import dev.caskeleton.adapter.outbound.persistence.postgresql.PostgreSqlPersistenceConfig;
-import dev.caskeleton.bootstrap.autoconfigure.jpa.JpaDataSourceProfileValidator;
-import dev.caskeleton.bootstrap.autoconfigure.jpa.JpaPlatformRuntimeAutoConfiguration;
-import dev.caskeleton.bootstrap.autoconfigure.jpa.PostgreSqlVersionPolicy;
import dev.caskeleton.bootstrap.runtime.startup.MigrationStartupConfig;
import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java
index 94d59707..940d849d 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.autoconfigure.jpa;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.Set;
@@ -29,7 +30,7 @@ public final class PostgreSqlVersionPolicy {
String product = metadata.getDatabaseProductName();
int major = metadata.getDatabaseMajorVersion();
if (!POSTGRESQL.equals(product) || !STABLE.contains(major)) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"Stable JPA profile requires PostgreSQL 16, 17 or 18, but the datasource reported "
+ product
+ ' '
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/DistributedLockConfig.java
similarity index 96%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/DistributedLockConfig.java
index 5ae09f17..ac962913 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/DistributedLockConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.lock;
+package dev.caskeleton.bootstrap.autoconfigure.lock;
import dev.caskeleton.application.lock.DistributedLockPort;
import io.micrometer.core.instrument.MeterRegistry;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPort.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPort.java
similarity index 91%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPort.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPort.java
index 6ec2bd1b..ba135f98 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPort.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPort.java
@@ -1,8 +1,9 @@
-package dev.caskeleton.bootstrap.lock;
+package dev.caskeleton.bootstrap.autoconfigure.lock;
import dev.caskeleton.application.lock.DistributedLock;
import dev.caskeleton.application.lock.DistributedLockPort;
import dev.caskeleton.application.lock.LockAcquisitionTimeoutException;
+import dev.caskeleton.bootstrap.runtime.RuntimeWarnings;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import java.time.Duration;
@@ -78,11 +79,8 @@ public final class MeteredDistributedLockPort implements DistributedLockPort {
try {
Counter.builder(LOCK_ACQUISITION).tag(TAG_OUTCOME, outcome).register(registry).increment();
} catch (RuntimeException ex) {
- log.warn(
- "distributed-lock metrics: failed to record counter {}[outcome={}]",
- LOCK_ACQUISITION,
- outcome,
- ex);
+ RuntimeWarnings.meterUnavailable(
+ "distributed-lock", LOCK_ACQUISITION + "[outcome=" + outcome + "]", ex);
}
}
@@ -93,7 +91,7 @@ public final class MeteredDistributedLockPort implements DistributedLockPort {
try {
Counter.builder(LOCK_LEASE_EXPIRED).register(registry).increment();
} catch (RuntimeException ex) {
- log.warn("distributed-lock metrics: failed to record counter {}", LOCK_LEASE_EXPIRED, ex);
+ RuntimeWarnings.meterUnavailable("distributed-lock", LOCK_LEASE_EXPIRED, ex);
}
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthConfig.java
similarity index 96%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthConfig.java
index 0b90a579..e05bd94d 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.mongo;
+package dev.caskeleton.bootstrap.autoconfigure.mongo;
import dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformHealthIndicator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributor.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributor.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributor.java
index 50bd9417..c5136913 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributor.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributor.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.mongo;
+package dev.caskeleton.bootstrap.autoconfigure.mongo;
import dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformHealthIndicator;
import java.util.Objects;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformCodecConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformCodecConfig.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformCodecConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformCodecConfig.java
index b121b53b..633ed21c 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformCodecConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformCodecConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.JacksonRoutingPlanCodec;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformDispatchConfig.java
similarity index 99%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformDispatchConfig.java
index 3fe9d35b..0e23488f 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformDispatchConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.NotificationDispatchProperties;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformObservabilityConfig.java
similarity index 93%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformObservabilityConfig.java
index 3933f2ad..ca081a5c 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformObservabilityConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationProviderAssembly;
@@ -6,6 +6,7 @@ import dev.caskeleton.adapter.outbound.notification.platform.dispatch.LoggingInb
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderRuntimeRegistry;
import dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotificationAudit;
import dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotificationMetrics;
+import dev.caskeleton.adapter.outbound.notification.platform.observation.MicrometerNotificationMetrics;
import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter;
import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationServingThresholds;
import dev.caskeleton.application.notification.platform.api.ProviderProfileId;
@@ -15,9 +16,8 @@ import dev.caskeleton.application.notification.platform.observation.Notification
import dev.caskeleton.application.notification.platform.observation.NotificationMetricsPort;
import dev.caskeleton.application.notification.platform.observation.NotificationSecurityAuditPort;
import dev.caskeleton.application.notification.platform.observation.NotificationServingStatePort;
-import dev.caskeleton.bootstrap.notification.observation.MicrometerNotificationMetrics;
-import dev.caskeleton.bootstrap.notification.observation.NotificationHealthEndpoint;
-import dev.caskeleton.bootstrap.notification.observation.NotificationObservationConvention;
+import dev.caskeleton.bootstrap.autoconfigure.notification.observation.NotificationHealthEndpoint;
+import dev.caskeleton.bootstrap.autoconfigure.notification.observation.NotificationObservationConvention;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformPolicyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformPolicyConfig.java
similarity index 99%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformPolicyConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformPolicyConfig.java
index d1f963b8..0273a6ac 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformPolicyConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformPolicyConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.application.notification.platform.dispatch.CanonicalNotificationPlanWriter;
import dev.caskeleton.application.notification.platform.dispatch.MessageDigestPort;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformProviderConfig.java
similarity index 99%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformProviderConfig.java
index fa5038a2..45b0445a 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformProviderConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationProviderAssembly;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRegistriesConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRegistriesConfig.java
similarity index 73%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRegistriesConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRegistriesConfig.java
index 3fc38021..7fe7d62d 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRegistriesConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRegistriesConfig.java
@@ -1,6 +1,8 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
+import dev.caskeleton.adapter.outbound.notification.platform.callback.MapProviderCallbackAdapterRegistry;
+import dev.caskeleton.adapter.outbound.notification.platform.callback.MapProviderEventProjectorRegistry;
import dev.caskeleton.application.notification.platform.api.ProviderId;
import dev.caskeleton.application.notification.platform.api.ProviderProfileId;
import dev.caskeleton.application.notification.platform.callback.CallbackLimits;
@@ -11,8 +13,6 @@ import dev.caskeleton.application.notification.platform.callback.ProviderEventPr
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@@ -38,8 +38,7 @@ public class NotificationPlatformRegistriesConfig {
List projectors) {
Map byProvider = new HashMap<>();
projectors.forEach(projector -> byProvider.put(projector.providerId(), projector));
- Map frozen = Map.copyOf(byProvider);
- return providerId -> Optional.ofNullable(frozen.get(providerId));
+ return new MapProviderEventProjectorRegistry(byProvider);
}
/** Callback adapter lookup by configured profile. */
@@ -61,30 +60,11 @@ public class NotificationPlatformRegistriesConfig {
}
});
- Map frozen = Map.copyOf(byProfile);
CallbackLimits limits =
new CallbackLimits(
settings.callbacks().maxBodyBytes(),
Set.of("application/json", "text/plain", "application/x-www-form-urlencoded"));
- return new ProviderCallbackAdapterRegistry() {
-
- @Override
- public ProviderCallbackAdapter require(ProviderProfileId profileId) {
- Objects.requireNonNull(profileId, "profileId");
- ProviderCallbackAdapter adapter = frozen.get(profileId);
- if (adapter == null) {
- // Refusing an unknown profile is a security property: an endpoint that accepted any
- // profile name would let a caller pick which signature check runs.
- throw new IllegalStateException("no callback adapter registered for the profile");
- }
- return adapter;
- }
-
- @Override
- public CallbackLimits limitsFor(ProviderProfileId profileId) {
- return limits;
- }
- };
+ return new MapProviderCallbackAdapterRegistry(byProfile, limits);
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRuntimeConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRuntimeConfig.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRuntimeConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRuntimeConfig.java
index cbbb8bd8..f1899231 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRuntimeConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRuntimeConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformSecretsConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformSecretsConfig.java
similarity index 96%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformSecretsConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformSecretsConfig.java
index ac69e3b7..b5fcab98 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformSecretsConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformSecretsConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationSecretRequirements;
@@ -6,6 +6,7 @@ import dev.caskeleton.adapter.outbound.notification.platform.security.SettingsSe
import dev.caskeleton.application.notification.platform.security.SecretKeyMaterial;
import dev.caskeleton.application.notification.platform.security.SecretMaterialProvider;
import dev.caskeleton.application.notification.platform.security.SecretPurpose;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.util.Base64;
import java.util.EnumMap;
import java.util.Map;
@@ -141,7 +142,7 @@ public class NotificationPlatformSecretsConfig {
boolean supplied = base64 != null && !base64.isBlank();
if (!supplied) {
if (required.contains(purpose)) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"notification secret for "
+ purpose
+ " is not configured; the capability that uses it is enabled, so the platform"
@@ -151,7 +152,7 @@ public class NotificationPlatformSecretsConfig {
}
String keyId = settings.activeKeyIds().get(purpose.name());
if (keyId == null || keyId.isBlank()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"no active key id configured for "
+ purpose
+ "; a constant id makes a rotation indistinguishable from the key it replaced, so"
@@ -181,13 +182,13 @@ public class NotificationPlatformSecretsConfig {
if (settings.activeKeyIds().containsValue(keyId)) {
// The same id under two different materials is the one state no reader can
// resolve: the envelope names an id and two keys answer to it.
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"key id " + keyId + " is both active and historical");
}
SecretKeyMaterial previous =
historical.put(keyId, material(purpose, keyId, base64));
if (previous != null) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"key id "
+ keyId
+ " is declared for both "
@@ -203,12 +204,12 @@ public class NotificationPlatformSecretsConfig {
/** Decodes and length-checks one key, active or retired. */
private static SecretKeyMaterial material(SecretPurpose purpose, String keyId, String base64) {
if (base64 == null || base64.isBlank()) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"notification secret " + keyId + " for " + purpose + " has no value");
}
byte[] material = decode(purpose, keyId, base64);
if (material.length < MINIMUM_KEY_BYTES) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"notification secret "
+ keyId
+ " for "
@@ -229,7 +230,7 @@ public class NotificationPlatformSecretsConfig {
} catch (IllegalArgumentException unknown) {
// Naming the purpose, never the keys under it: a typo here would otherwise be reported by
// echoing the block that was misfiled.
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"historical notification secrets are declared under key purpose "
+ name
+ ", which is not a purpose this platform issues keys for",
@@ -243,7 +244,7 @@ public class NotificationPlatformSecretsConfig {
} catch (IllegalArgumentException malformed) {
// The id and the purpose, never the value, and never the decoder's own message — a base64
// failure report can quote the input it choked on.
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"notification secret " + keyId + " for " + purpose + " is not valid base64");
}
}
@@ -267,7 +268,7 @@ public class NotificationPlatformSecretsConfig {
String fingerprint = java.util.HexFormat.of().formatHex(key.material());
SecretKeyMaterial previous = seen.put(fingerprint, key);
if (previous != null) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"notification secrets "
+ previous.keyId()
+ " ("
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformWorkerConfig.java
similarity index 99%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformWorkerConfig.java
index 01cd5fb0..de247b65 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformWorkerConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.LeaseRecoveryService;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.NotificationBackgroundWorkers;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationRootAutoConfiguration.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationRootAutoConfiguration.java
index abdc586e..cd152a3c 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationRootAutoConfiguration.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import dev.caskeleton.adapter.outbound.notification.NotificationConfig;
import dev.caskeleton.adapter.outbound.notification.NotificationRoutesSettings;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationHealthEndpoint.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationHealthEndpoint.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationHealthEndpoint.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationHealthEndpoint.java
index 37e49612..889be11a 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationHealthEndpoint.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationHealthEndpoint.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification.observation;
+package dev.caskeleton.bootstrap.autoconfigure.notification.observation;
import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter;
import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthSnapshot;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationContext.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationContext.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationContext.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationContext.java
index fe7fcb70..064db3cf 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationContext.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationContext.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification.observation;
+package dev.caskeleton.bootstrap.autoconfigure.notification.observation;
import io.micrometer.observation.Observation;
import java.util.LinkedHashMap;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationConvention.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationConvention.java
index d3df92bd..44a1da60 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationConvention.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification.observation;
+package dev.caskeleton.bootstrap.autoconfigure.notification.observation;
import dev.caskeleton.application.notification.platform.observation.CardinalityGuard;
import io.micrometer.common.KeyValue;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfig.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfig.java
index 5983fdb7..8b14c416 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import dev.caskeleton.application.outbox.OutboxBackoffPolicy;
import dev.caskeleton.application.outbox.OutboxMessagePublishPort;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxLeaderElectionToken.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxLeaderElectionToken.java
similarity index 92%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxLeaderElectionToken.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxLeaderElectionToken.java
index 5d79de32..35c167d1 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxLeaderElectionToken.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxLeaderElectionToken.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
/**
* Marker bean: presence in the context satisfies the multi-instance coordination requirement (no
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxMetrics.java
similarity index 89%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxMetrics.java
index cba18518..08bc1e8b 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxMetrics.java
@@ -1,8 +1,9 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import dev.caskeleton.application.outbox.OutboxEventStatus;
import dev.caskeleton.application.outbox.OutboxRelayResult;
import dev.caskeleton.application.outbox.OutboxStorePort;
+import dev.caskeleton.bootstrap.runtime.RuntimeWarnings;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MultiGauge;
@@ -11,8 +12,6 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
/**
@@ -21,8 +20,6 @@ import org.springframework.beans.factory.ObjectProvider;
*/
public final class OutboxMetrics {
- private static final Logger log = LoggerFactory.getLogger(OutboxMetrics.class);
-
private static final String COUNTER_NAME = "outbox.publisher.published.total";
private static final String SIZE_GAUGE = "outbox.pending.size";
private static final String LAG_GAUGE = "outbox.publisher.lag";
@@ -65,10 +62,9 @@ public final class OutboxMetrics {
.register(registry)
.increment();
} catch (Exception ex) {
- log.warn(
- "outbox metrics: failed to record counter for eventType={} outcome={}",
- eo.eventType(),
- eo.outcome(),
+ RuntimeWarnings.meterUnavailable(
+ "outbox",
+ COUNTER_NAME + "[eventType=" + eo.eventType() + ",outcome=" + eo.outcome() + "]",
ex);
}
}
@@ -98,7 +94,7 @@ public final class OutboxMetrics {
}
pendingSizeGauge.register(rows, true);
} catch (Exception ex) {
- log.warn("outbox metrics: failed to refresh pending-size gauge", ex);
+ RuntimeWarnings.meterUnavailable("outbox", "pending-size gauge", ex);
}
}
@@ -111,7 +107,7 @@ public final class OutboxMetrics {
}
publisherLagGauge.register(rows, true);
} catch (Exception ex) {
- log.warn("outbox metrics: failed to refresh publisher-lag gauge", ex);
+ RuntimeWarnings.meterUnavailable("outbox", "publisher-lag gauge", ex);
}
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidator.java
similarity index 93%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidator.java
index 83f20605..e2227b31 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidator.java
@@ -1,5 +1,6 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.util.Objects;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.env.Environment;
@@ -51,7 +52,7 @@ public final class OutboxRelayBrokerRequirementValidator implements Initializing
if (!relayEnabled || !broker.isBlank()) {
return;
}
- throw new IllegalStateException(
+ throw StartupFailures.requiredAdapterDisabled(
RELAY_KEY
+ " is enabled but "
+ BROKER_KEY
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayScheduler.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayScheduler.java
index 233d661d..5985272f 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayScheduler.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import dev.caskeleton.application.outbox.OutboxRelayResult;
import dev.caskeleton.application.outbox.PublishPendingOutboxEventsCommand;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettings.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxSettings.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettings.java
index 01899318..f12a4eca 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxSettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettings.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.time.Duration;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidator.java
similarity index 73%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidator.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidator.java
index 96b7675d..5b21d899 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidator.java
@@ -1,8 +1,8 @@
-package dev.caskeleton.bootstrap.runtime;
+package dev.caskeleton.bootstrap.autoconfigure.redis;
-import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
-import java.util.ArrayList;
-import java.util.List;
+import dev.caskeleton.bootstrap.runtime.SecretSourceValidator;
+import dev.caskeleton.bootstrap.runtime.startup.StartupPhase;
+import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext;
import java.util.Map;
import java.util.Objects;
import org.springframework.beans.factory.SmartInitializingSingleton;
@@ -43,25 +43,23 @@ public class RedisActivationValidator implements SmartInitializingSingleton {
if (isRedisGloballyEnabled()) {
return;
}
- List contradictions = new ArrayList<>();
+ StartupValidationContext contradictions =
+ StartupValidationContext.forPhase(StartupPhase.ADAPTER_ENABLEMENT);
REDIS_SELECTING_VALUES.forEach(
(property, selectingValue) -> {
String configured = environment.getProperty(property);
if (selectingValue.equalsIgnoreCase(configured)) {
- contradictions.add(property + "=" + configured);
+ contradictions.reject(property + "=" + configured);
}
});
- if (contradictions.isEmpty()) {
- return;
- }
- contradictions.sort(String::compareTo);
- throw StartupFailures.requiredAdapterDisabled(
- "Redis is disabled ("
- + SecretSourceValidator.REDIS_ENABLED_PROPERTY
- + "=false, env APP_REDIS_ENABLED) but these roles select it: "
- + contradictions
- + " — set APP_REDIS_ENABLED=true to compose Redis, or point each role at a "
- + "non-Redis provider. A role selector never activates Redis on its own.");
+ contradictions
+ .sorted()
+ .failIfViolated(
+ "Redis is disabled ("
+ + SecretSourceValidator.REDIS_ENABLED_PROPERTY
+ + "=false, env APP_REDIS_ENABLED) but these roles select it",
+ "set APP_REDIS_ENABLED=true to compose Redis, or point each role at a non-Redis "
+ + "provider. A role selector never activates Redis on its own.");
}
private boolean isRedisGloballyEnabled() {
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java
new file mode 100644
index 00000000..91c76e99
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java
@@ -0,0 +1,60 @@
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+
+import dev.caskeleton.adapter.outbound.cache.redis.cache.RedisCacheRegionAdapter;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner;
+import dev.caskeleton.application.cache.CacheRegionPort;
+import dev.caskeleton.bootstrap.runtime.SecretSource;
+import java.time.Clock;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * The Redis-backed semantic cache region.
+ *
+ *
One capability, one configuration class (BOOT-007). It contributes nothing unless the cache
+ * binding names Redis, and it is only reachable at all while the Redis master switch is on, because
+ * {@link RedisCapabilityConfig} imports it and carries that switch.
+ */
+@Configuration(proxyBeanMethods = false)
+class RedisCacheConfig {
+
+ /**
+ * The semantic cache region for the default binding.
+ *
+ * @param owner the Redis runtime owner
+ * @param sdk the Redis SDK settings, which own the namespace
+ * @param capabilities the capability settings
+ * @param secretSource resolves the key-digest material
+ * @param clock the clock expiries are measured against
+ * @return the cache region port
+ */
+ @Bean
+ @ConditionalOnProperty(
+ name = "ca-skeleton.capabilities.cache.bindings.default",
+ havingValue = "redis")
+ CacheRegionPort redisDefaultCacheRegion(
+ RedisRuntimeOwner owner,
+ RedisSdkSettings sdk,
+ RedisCapabilitySettings capabilities,
+ SecretSource secretSource,
+ Clock clock) {
+ RedisCapabilitySettings.Cache cache = capabilities.getCache();
+ cache.validate();
+ RedisKeys.KeyDigest digest =
+ RedisKeys.KeyDigest.of(secretSource, cache.getKeyHmacSecretReference(), "cache", sdk);
+ return new RedisCacheRegionAdapter<>(
+ owner,
+ new RedisCacheRegionAdapter.CacheKeys(
+ RedisKeys.namespaceOf(sdk), cache.getSemanticRegion(), cache.getKeyVersion()),
+ digest::of,
+ value -> value,
+ stored -> stored,
+ clock,
+ cache.getPositiveSoftTtl(),
+ cache.getPositiveHardTtl(),
+ cache.getNegativeTtl(),
+ cache.getCommandTimeout());
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java
new file mode 100644
index 00000000..14a1cd35
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java
@@ -0,0 +1,68 @@
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkAutoConfiguration;
+import dev.caskeleton.bootstrap.runtime.SecretSource;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * Composes the Redis semantic ports the deployment's role selectors asked for.
+ *
+ *
Before this existed, {@code APP_REDIS_ENABLED=true} produced a client, a connection owner and
+ * a health contributor — and nothing else. Every semantic port stayed unimplemented, so a
+ * deployment that selected {@code redis} for its rate limiter got a context that started, reported
+ * healthy, and had no rate limiter. "Redis is reachable" is not the same claim as "the capabilities
+ * that need Redis are available", and only the second one is worth making.
+ *
+ *
Each port is conditional on its own selector, so selecting Redis for leases does not build an
+ * idempotency store nobody asked for. All of them are conditional on {@link RedisRuntimeOwner},
+ * which exists only while the global switch is on — a role selector never activates Redis by
+ * itself, and {@code RedisActivationValidator} refuses the contradiction of a role that selects
+ * Redis while the switch is off.
+ *
+ *
This class is the composition point only: it holds the master switch, binds the settings once,
+ * bridges the secret backend into the SDK, and imports one configuration per capability. It used to
+ * hold all four capabilities plus rate-limit policy compilation and the key digest — 376 lines, in
+ * a leaf whose Notification platform had already been split into nine focused configurations. The
+ * decomposition pattern existed; this was simply where it had not been applied (BOOT-007).
+ *
+ *
The namespace comes from {@code app.redis.namespace} for every capability. That is the change
+ * that makes the ACL pattern meaningful: one prefix, {@code {environment}:{service}:{domain}}, that
+ * the deployment's {@code ~} pattern can actually fence.
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableConfigurationProperties(RedisCapabilitySettings.class)
+// The global switch, not a role selector. A role selector says which capability composes; it
+// never says Redis exists, and RedisActivationValidator refuses the contradiction of one
+// without the other.
+@ConditionalOnProperty(prefix = "app.redis", name = "enabled", havingValue = "true")
+// Imported rather than scanned: the master switch above gates this class, and an @Import only
+// registers what a registered configuration asked for. Component scanning would have made each
+// capability configuration independently discoverable and the switch advisory.
+@Import({
+ RedisCacheConfig.class,
+ RedisRateLimitConfig.class,
+ RedisLeaseConfig.class,
+ RedisIdempotencyConfig.class
+})
+public class RedisCapabilityConfig {
+
+ /**
+ * Bridges the deployment's configured secret backend into the Redis SDK.
+ *
+ *
Without it the SDK falls back to reading the process environment directly, which quietly
+ * bypasses whichever backend {@code ca-skeleton.secret-source.strategy} selected — so a
+ * deployment using a secret manager would resolve its Redis credential from an environment
+ * variable that is not supposed to hold it.
+ *
+ * @param secretSource the configured backend
+ * @return the SDK's view of it
+ */
+ @Bean
+ RedisSdkAutoConfiguration.RedisSecretSource redisSdkSecretSource(SecretSource secretSource) {
+ return secretSource::resolve;
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilitySettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilitySettings.java
similarity index 97%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilitySettings.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilitySettings.java
index 2de8d527..068a263f 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilitySettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilitySettings.java
@@ -1,5 +1,6 @@
-package dev.caskeleton.bootstrap.redis;
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -69,7 +70,7 @@ public class RedisCapabilitySettings {
void validate() {
if (positiveSoftTtl.compareTo(positiveHardTtl) > 0) {
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"the cache soft TTL must not exceed the hard TTL: an entry cannot stop being fresh"
+ " after it has stopped being usable");
}
@@ -77,17 +78,17 @@ public class RedisCapabilitySettings {
// The floor exists so an operator cannot configure a cache whose entries expire faster
// than the round trip that wrote them, which costs a Redis write per read and returns a
// miss every time.
- throw new IllegalStateException(
+ throw StartupFailures.envValidation(
"the cache hard TTL of "
+ positiveHardTtl
+ " is below the configured minimum of "
+ minimumHardTtl);
}
if (commandTimeout.isZero() || commandTimeout.isNegative()) {
- throw new IllegalStateException("the cache command timeout must be positive");
+ throw StartupFailures.envValidation("the cache command timeout must be positive");
}
if (keyVersion < 1) {
- throw new IllegalStateException("the cache key version must be positive");
+ throw StartupFailures.envValidation("the cache key version must be positive");
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java
new file mode 100644
index 00000000..f94def89
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java
@@ -0,0 +1,79 @@
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+
+import dev.caskeleton.adapter.outbound.cache.redis.idempotency.IdempotencyScripts;
+import dev.caskeleton.adapter.outbound.cache.redis.idempotency.RedisIdempotencyStoreAdapter;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner;
+import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2;
+import dev.caskeleton.application.idempotency.v2.IdempotencyStorePortV2;
+import java.time.Clock;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * The Redis-backed owner-safe idempotency store and the executor that drives it.
+ *
+ *
Both beans stay together because the selection guard in {@code
+ * IdempotencyProviderSelectionConfig} refuses a store with no executor: they are one selection, not
+ * two (BOOT-007).
+ */
+@Configuration(proxyBeanMethods = false)
+class RedisIdempotencyConfig {
+
+ /**
+ * The owner-safe request-replay store.
+ *
+ * @param owner the Redis runtime owner
+ * @param sdk the Redis SDK settings, which own the namespace
+ * @param capabilities the capability settings
+ * @param clock the clock lease deadlines are measured against
+ * @return the V2 store port
+ */
+ @Bean
+ @ConditionalOnProperty(
+ name = "ca-skeleton.capabilities.idempotency.provider",
+ havingValue = "redis")
+ IdempotencyStorePortV2 redisIdempotencyStore(
+ RedisRuntimeOwner owner,
+ RedisSdkSettings sdk,
+ RedisCapabilitySettings capabilities,
+ Clock clock) {
+ RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency();
+ return new RedisIdempotencyStoreAdapter(
+ owner,
+ new RedisIdempotencyStoreAdapter.IdempotencyKeys(
+ RedisKeys.namespaceOf(sdk), idempotency.getKeyVersion()),
+ new IdempotencyScripts(),
+ clock,
+ idempotency.getCommandTimeout());
+ }
+
+ /**
+ * The request-replay lifecycle driven over the selected owner-safe store.
+ *
+ *
Provider-neutral by type — it depends on {@link IdempotencyStorePortV2}, not on Redis — and
+ * composed here because this is where the settings behind it are bound. A selection that produced
+ * the store and no executor produced a store nothing could drive, which is what {@code
+ * IdempotencyProviderSelectionConfig} refuses.
+ *
+ * @param store the selected owner-safe store
+ * @param capabilities the capability settings
+ * @return the owner-safe request-replay executor
+ */
+ @Bean
+ @ConditionalOnProperty(
+ name = "ca-skeleton.capabilities.idempotency.provider",
+ havingValue = "redis")
+ IdempotencyExecutorV2 idempotencyExecutorV2(
+ IdempotencyStorePortV2 store, RedisCapabilitySettings capabilities) {
+ RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency();
+ return new IdempotencyExecutorV2(
+ store,
+ idempotency.getProcessingLease(),
+ idempotency.getReplayTtl(),
+ idempotency.getFailureRetention(),
+ idempotency.getResponseCodecId(),
+ idempotency.getPolicyRevision());
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java
new file mode 100644
index 00000000..19aa3c25
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java
@@ -0,0 +1,103 @@
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisNamespace;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings;
+import dev.caskeleton.bootstrap.runtime.SecretSource;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Namespace construction and the keyed digest every Redis capability derives its key material from.
+ *
+ *
Both were private members of the single Redis configuration class. Splitting that class by
+ * capability (BOOT-007) made them shared, and shared members of a class that no longer exists have
+ * to live somewhere: here, where the two things a Redis key is made of — the deployment's namespace
+ * and the digest that hides identifiers inside it — sit together and are named for what they are.
+ */
+final class RedisKeys {
+
+ private RedisKeys() {}
+
+ /** The deployment's key namespace: {@code {environment}:{service}:{domain}}. */
+ static RedisNamespace namespaceOf(RedisSdkSettings settings) {
+ return new RedisNamespace(
+ settings.getNamespace().getEnvironment(),
+ settings.getNamespace().getService(),
+ settings.getNamespace().getDomain());
+ }
+
+ /**
+ * Digests a semantic key before it reaches Redis.
+ *
+ *
Keyed, not plain. An unkeyed digest of a low-entropy identifier — a customer number, a short
+ * order id — is reversible by anyone who can read the keyspace and can guess the format, which is
+ * everyone who can run {@code SCAN}. The key comes from the deployment's secret backend, so the
+ * keyspace is only correlatable by something that already holds the secret.
+ */
+ record KeyDigest(javax.crypto.Mac prototype) {
+
+ static KeyDigest of(
+ SecretSource secretSource, String reference, String capability, RedisSdkSettings sdk) {
+ String name = secretName(reference, capability);
+ String material =
+ secretSource
+ .resolve(name)
+ .orElseThrow(
+ () ->
+ StartupFailures.envValidation(
+ "the Redis "
+ + capability
+ + " key-digest secret '"
+ + name
+ + "' resolved to nothing. Without it the keyspace would carry"
+ + " identifiers in a form anybody who can read it could reverse."));
+ try {
+ javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
+ mac.init(
+ new javax.crypto.spec.SecretKeySpec(
+ // Namespace-bound: the same identifier in staging and production must not digest
+ // to the same key, or a keyspace dump from the softer environment would map
+ // one-for-one onto the harder one.
+ (material
+ + '|'
+ + sdk.getNamespace().getEnvironment()
+ + ':'
+ + sdk.getNamespace().getService()
+ + ':'
+ + sdk.getNamespace().getDomain())
+ .getBytes(StandardCharsets.UTF_8),
+ "HmacSHA256"));
+ return new KeyDigest(mac);
+ } catch (java.security.GeneralSecurityException failure) {
+ throw new IllegalStateException("HmacSHA256 must be available", failure);
+ }
+ }
+
+ private static String secretName(String reference, String capability) {
+ if (reference == null || reference.isBlank()) {
+ throw StartupFailures.envValidation(
+ "the Redis " + capability + " capability needs a key-digest secret reference");
+ }
+ int separator = reference.lastIndexOf('/');
+ if (!reference.startsWith("secret://") || separator < 0) {
+ throw StartupFailures.envValidation(
+ "the Redis "
+ + capability
+ + " key-digest reference '"
+ + reference
+ + "' must be secret:///");
+ }
+ return reference.substring(separator + 1);
+ }
+
+ String of(String key) {
+ try {
+ javax.crypto.Mac mac = (javax.crypto.Mac) prototype.clone();
+ return "hv1:"
+ + java.util.HexFormat.of().formatHex(mac.doFinal(key.getBytes(StandardCharsets.UTF_8)));
+ } catch (CloneNotSupportedException failure) {
+ throw new IllegalStateException("a Mac must be cloneable", failure);
+ }
+ }
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java
new file mode 100644
index 00000000..a3f22457
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java
@@ -0,0 +1,45 @@
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+
+import dev.caskeleton.adapter.outbound.cache.redis.lease.LeaseScripts;
+import dev.caskeleton.adapter.outbound.cache.redis.lease.RedisDistributedLeaseAdapter;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner;
+import dev.caskeleton.application.lease.DistributedLeasePort;
+import java.time.Clock;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/** The Redis-backed distributed lease. Efficiency only — it never supplies fencing. */
+@Configuration(proxyBeanMethods = false)
+class RedisLeaseConfig {
+
+ /**
+ * The distributed lease port. Efficiency only — it never supplies fencing.
+ *
+ * @param owner the Redis runtime owner
+ * @param sdk the Redis SDK settings, which own the namespace
+ * @param capabilities the capability settings
+ * @param clock the wall clock used for reporting instants
+ * @return the lease port
+ */
+ @Bean
+ @ConditionalOnProperty(name = "ca-skeleton.capabilities.lease.provider", havingValue = "redis")
+ DistributedLeasePort redisDistributedLeasePort(
+ RedisRuntimeOwner owner,
+ RedisSdkSettings sdk,
+ RedisCapabilitySettings capabilities,
+ Clock clock) {
+ RedisCapabilitySettings.Lease lease = capabilities.getLease();
+ return new RedisDistributedLeaseAdapter(
+ owner,
+ new RedisDistributedLeaseAdapter.LeaseKeys(
+ RedisKeys.namespaceOf(sdk), lease.getKeyVersion()),
+ new LeaseScripts(),
+ clock,
+ System::nanoTime,
+ lease.getCommandTimeout(),
+ lease.getContentionRetryAfter(),
+ lease.getDriftBudget());
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java
new file mode 100644
index 00000000..e7905454
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java
@@ -0,0 +1,133 @@
+package dev.caskeleton.bootstrap.autoconfigure.redis;
+
+import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitKeys;
+import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitScripts;
+import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RedisEdgeRateLimitAdapter;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings;
+import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner;
+import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.shared.ratelimit.EdgeRateLimitPort;
+import dev.caskeleton.shared.ratelimit.RateLimitAlgorithm;
+import dev.caskeleton.shared.ratelimit.RateLimitFailurePolicy;
+import dev.caskeleton.shared.ratelimit.RateLimitPolicy;
+import dev.caskeleton.shared.ratelimit.RateParameters;
+import java.time.Clock;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * The Redis-backed edge rate limiter, and the policy compilation it needs.
+ *
+ *
The two private helpers below are the largest part of what used to make the single Redis
+ * configuration class 376 lines: policy compilation belongs to the rate limiter and to nothing
+ * else, so it moves with it (BOOT-007).
+ */
+@Configuration(proxyBeanMethods = false)
+class RedisRateLimitConfig {
+
+ /**
+ * The provider-neutral edge rate limiter, backed by Redis.
+ *
+ * @param owner the Redis runtime owner
+ * @param sdk the Redis SDK settings, which own the namespace
+ * @param capabilities the capability settings
+ * @param clock the clock decision windows are measured against
+ * @return the rate limit port
+ */
+ @Bean
+ @ConditionalOnProperty(
+ name = "ca-skeleton.capabilities.rate-limit.provider",
+ havingValue = "redis")
+ EdgeRateLimitPort redisEdgeRateLimitPort(
+ RedisRuntimeOwner owner,
+ RedisSdkSettings sdk,
+ RedisCapabilitySettings capabilities,
+ Clock clock) {
+ RedisCapabilitySettings.RateLimit rateLimit = capabilities.getRateLimit();
+ return new RedisEdgeRateLimitAdapter(
+ owner,
+ new RateLimitKeys(RedisKeys.namespaceOf(sdk), rateLimit.getKeyVersion()),
+ policiesOf(rateLimit),
+ new RateLimitScripts(),
+ clock,
+ rateLimit.getCommandTimeout(),
+ rateLimit.getFailureRetryAfter());
+ }
+
+ private static Map policiesOf(
+ RedisCapabilitySettings.RateLimit rateLimit) {
+ if (rateLimit.getPolicies().isEmpty()) {
+ // An enabled limiter with no policy would answer every request "unknown policy", which the
+ // port reports as a deployment error on every call rather than once at startup.
+ throw StartupFailures.envValidation(
+ "the Redis rate limiter is selected but no policy is configured under"
+ + " ca-skeleton.capabilities.rate-limit.policies");
+ }
+ Map policies = new LinkedHashMap<>();
+ rateLimit
+ .getPolicies()
+ .forEach((id, policy) -> policies.put(id, policyOf(id, policy, rateLimit)));
+ if (!policies.containsKey(rateLimit.getDefaultPolicyId())) {
+ throw StartupFailures.envValidation(
+ "the default rate-limit policy '"
+ + rateLimit.getDefaultPolicyId()
+ + "' is not among the configured policies "
+ + policies.keySet());
+ }
+ return policies;
+ }
+
+ private static RateLimitPolicy policyOf(
+ String id,
+ RedisCapabilitySettings.RateLimit.Policy policy,
+ RedisCapabilitySettings.RateLimit rateLimit) {
+ RateLimitAlgorithm algorithm =
+ switch (policy.getAlgorithm().toLowerCase(Locale.ROOT)) {
+ case "fixed-window" -> RateLimitAlgorithm.FIXED_WINDOW;
+ case "sliding-counter" -> RateLimitAlgorithm.SLIDING_COUNTER;
+ case "token-bucket" -> RateLimitAlgorithm.TOKEN_BUCKET;
+ default ->
+ throw StartupFailures.envValidation(
+ "unsupported rate-limit algorithm '" + policy.getAlgorithm() + "' for " + id);
+ };
+ RateParameters parameters =
+ switch (algorithm) {
+ case FIXED_WINDOW ->
+ new RateParameters.FixedWindow(policy.getLimit(), policy.getWindow());
+ case SLIDING_COUNTER ->
+ new RateParameters.SlidingCounter(policy.getLimit(), policy.getWindow());
+ case TOKEN_BUCKET ->
+ new RateParameters.TokenBucket(
+ policy.getCapacity(), policy.getRefillTokens(), policy.getRefillPeriod());
+ // No `default`. RateLimitAlgorithm has exactly these three constants, so the branch
+ // that
+ // used to be here was unreachable — and it suppressed the compiler's exhaustiveness
+ // check
+ // on this switch expression, which is the one thing that would notice a fourth
+ // algorithm
+ // being added without parameters for it (BOOT-010).
+ };
+ if (!"fail-closed".equalsIgnoreCase(rateLimit.getFailurePolicy())) {
+ // The port only implements fail-closed, so a deployment that configured anything else has a
+ // limit it believes is permissive and is not.
+ throw StartupFailures.envValidation(
+ "only fail-closed is supported for the Redis rate limiter, but"
+ + " ca-skeleton.capabilities.rate-limit.failure-policy is '"
+ + rateLimit.getFailurePolicy()
+ + "'");
+ }
+ return new RateLimitPolicy(
+ id,
+ policy.getRevision(),
+ algorithm,
+ parameters,
+ policy.getMaximumCost(),
+ policy.getCleanupGrace(),
+ policy.getMaximumClockRegression(),
+ RateLimitFailurePolicy.FAIL_CLOSED);
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessor.java
similarity index 69%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessor.java
index 2c5d4ecb..373b5adf 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessor.java
@@ -1,16 +1,13 @@
-package dev.caskeleton.bootstrap.runtime;
+package dev.caskeleton.bootstrap.autoconfigure.redis;
import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisCorrectnessRoles;
-import java.util.Arrays;
-import java.util.LinkedHashSet;
-import java.util.Map;
-import java.util.Set;
+import dev.caskeleton.bootstrap.runtime.ReadinessGroup;
+import dev.caskeleton.bootstrap.runtime.SecretSourceValidator;
+import dev.caskeleton.bootstrap.runtime.startup.StartupOrder;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
-import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
-import org.springframework.core.env.MapPropertySource;
/**
* Adds the Redis readiness contributor to the readiness group, and only when it will exist.
@@ -35,8 +32,6 @@ import org.springframework.core.env.MapPropertySource;
*/
public class RedisReadinessGroupPostProcessor implements EnvironmentPostProcessor, Ordered {
- static final String READINESS_INCLUDE = "management.endpoint.health.group.readiness.include";
-
private static final String PROPERTY_SOURCE = "redisReadinessGroup";
@Override
@@ -45,22 +40,8 @@ public class RedisReadinessGroupPostProcessor implements EnvironmentPostProcesso
if (!redisEnabled(environment) || !RedisCorrectnessRoles.anySelected(environment)) {
return;
}
- String configured = environment.getProperty(READINESS_INCLUDE, "");
- Set members = new LinkedHashSet<>();
- Arrays.stream(configured.split(","))
- .map(String::trim)
- .filter(member -> !member.isEmpty())
- .forEach(members::add);
- if (!members.add(RedisCorrectnessRoles.REQUIRED_HEALTH_CONTRIBUTOR)) {
- // Already named explicitly. Adding a source that repeats it would be harmless but would also
- // hide that the operator asked for it themselves.
- return;
- }
- environment
- .getPropertySources()
- .addFirst(
- new MapPropertySource(
- PROPERTY_SOURCE, Map.of(READINESS_INCLUDE, String.join(",", members))));
+ ReadinessGroup.include(
+ environment, PROPERTY_SOURCE, RedisCorrectnessRoles.REQUIRED_HEALTH_CONTRIBUTOR);
}
private static boolean redisEnabled(ConfigurableEnvironment environment) {
@@ -73,6 +54,6 @@ public class RedisReadinessGroupPostProcessor implements EnvironmentPostProcesso
// After config data: the readiness group this appends to is defined in application.yml, and
// reading it before that file is loaded would append to an empty string and silently drop
// readinessState and db from the group.
- return ConfigDataEnvironmentPostProcessor.ORDER + 1;
+ return StartupOrder.READINESS_REDIS;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorder.java
similarity index 92%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorder.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorder.java
index fcc358c5..eb601934 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorder.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorder.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import dev.caskeleton.shared.tracing.SpanErrorRecorder;
import io.micrometer.tracing.Span;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingConfig.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingConfig.java
index 4cb25546..57d32136 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingConfig.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import dev.caskeleton.shared.tracing.SpanErrorRecorder;
import io.micrometer.core.instrument.MeterRegistry;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolver.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolver.java
similarity index 96%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolver.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolver.java
index 639f43fd..8d235798 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolver.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolver.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import java.util.Locale;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessor.java
similarity index 72%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessor.java
index 38e25a18..bd43309f 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessor.java
@@ -1,8 +1,10 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
+import dev.caskeleton.bootstrap.runtime.startup.StartupOrder;
import java.util.Map;
import org.springframework.boot.EnvironmentPostProcessor;
import org.springframework.boot.SpringApplication;
+import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
@@ -11,7 +13,7 @@ import org.springframework.core.env.MapPropertySource;
* management.tracing.sampling.probability}. Registered via {@code META-INF/spring.factories}. See
* README for the design rationale.
*/
-public class TracingSamplingEnvironmentPostProcessor implements EnvironmentPostProcessor {
+public class TracingSamplingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
private static final String NATIVE_KEY = "management.tracing.sampling.probability";
private static final String CA_SAMPLE_RATE_KEY = "ca-skeleton.tracing.sample-rate";
@@ -35,4 +37,12 @@ public class TracingSamplingEnvironmentPostProcessor implements EnvironmentPostP
new MapPropertySource(
"tracingSamplingBridge", Map.of(NATIVE_KEY, String.valueOf(rate))));
}
+
+ @Override
+ public int getOrder() {
+ // Declared rather than inherited. Implementing EnvironmentPostProcessor without Ordered means
+ // LOWEST_PRECEDENCE by default, which is the same value three other post-processors returned
+ // explicitly — four things claiming the same position, none of them saying so (BOOT-005).
+ return StartupOrder.TRACING_SAMPLING;
+ }
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingRateGauge.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingRateGauge.java
similarity index 81%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingRateGauge.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingRateGauge.java
index 31061adc..d66bd7a8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingRateGauge.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingRateGauge.java
@@ -1,9 +1,8 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
+import dev.caskeleton.bootstrap.runtime.RuntimeWarnings;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
/**
@@ -13,8 +12,6 @@ import org.springframework.beans.factory.ObjectProvider;
*/
public final class TracingSamplingRateGauge {
- private static final Logger log = LoggerFactory.getLogger(TracingSamplingRateGauge.class);
-
public static final String METRIC_NAME = "tracing.sampling.rate";
public static final String TAG_PROFILE = "profile";
@@ -35,7 +32,7 @@ public final class TracingSamplingRateGauge {
.description("Effective distributed-tracing sample rate for the active profile")
.register(registry);
} catch (RuntimeException ex) {
- log.warn("tracing metrics: failed to register gauge {}", METRIC_NAME, ex);
+ RuntimeWarnings.meterUnavailable("tracing", METRIC_NAME, ex);
}
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettings.java
similarity index 98%
rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSettings.java
rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettings.java
index 0038e641..39f8fb6f 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettings.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
import java.net.URI;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java
deleted file mode 100644
index b6b61131..00000000
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package dev.caskeleton.bootstrap.idempotency;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.boot.context.properties.bind.ConstructorBinding;
-
-/** Exact provider selector; provider precedence is never inferred from the classpath. */
-@ConfigurationProperties(prefix = "ca-skeleton.capabilities.idempotency")
-public record IdempotencyProviderSettings(Provider provider) {
-
- @ConstructorBinding
- public IdempotencyProviderSettings {
- provider = provider == null ? Provider.JDBC : provider;
- }
-
- /**
- * The stores a deployment may select.
- *
- *
{@code POSTGRESQL} selects the owner-safe V2 store in the JPA leaf. It had no value here for
- * as long as it existed, so the only way to reach it was to construct it in a test: a capability
- * with an implementation, a schema stream and an integration suite that no deployment could turn
- * on. A store nobody can select is not a feature, and leaving the selector out did not make the
- * code any less present in the artifact.
- */
- public enum Provider {
- DISABLED,
- JDBC,
- REDIS,
- POSTGRESQL
- }
-}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java
deleted file mode 100644
index 651b61f5..00000000
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java
+++ /dev/null
@@ -1,376 +0,0 @@
-package dev.caskeleton.bootstrap.redis;
-
-import dev.caskeleton.adapter.outbound.cache.redis.cache.RedisCacheRegionAdapter;
-import dev.caskeleton.adapter.outbound.cache.redis.idempotency.IdempotencyScripts;
-import dev.caskeleton.adapter.outbound.cache.redis.idempotency.RedisIdempotencyStoreAdapter;
-import dev.caskeleton.adapter.outbound.cache.redis.lease.LeaseScripts;
-import dev.caskeleton.adapter.outbound.cache.redis.lease.RedisDistributedLeaseAdapter;
-import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitKeys;
-import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitScripts;
-import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RedisEdgeRateLimitAdapter;
-import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisNamespace;
-import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkAutoConfiguration;
-import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings;
-import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner;
-import dev.caskeleton.application.cache.CacheRegionPort;
-import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2;
-import dev.caskeleton.application.idempotency.v2.IdempotencyStorePortV2;
-import dev.caskeleton.application.lease.DistributedLeasePort;
-import dev.caskeleton.bootstrap.runtime.SecretSource;
-import dev.caskeleton.shared.ratelimit.EdgeRateLimitPort;
-import dev.caskeleton.shared.ratelimit.RateLimitAlgorithm;
-import dev.caskeleton.shared.ratelimit.RateLimitFailurePolicy;
-import dev.caskeleton.shared.ratelimit.RateLimitPolicy;
-import dev.caskeleton.shared.ratelimit.RateParameters;
-import java.nio.charset.StandardCharsets;
-import java.time.Clock;
-import java.util.LinkedHashMap;
-import java.util.Locale;
-import java.util.Map;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * Composes the Redis semantic ports the deployment's role selectors asked for.
- *
- *
Before this existed, {@code APP_REDIS_ENABLED=true} produced a client, a connection owner and
- * a health contributor — and nothing else. Every semantic port stayed unimplemented, so a
- * deployment that selected {@code redis} for its rate limiter got a context that started, reported
- * healthy, and had no rate limiter. "Redis is reachable" is not the same claim as "the capabilities
- * that need Redis are available", and only the second one is worth making.
- *
- *
Each port is conditional on its own selector, so selecting Redis for leases does not build an
- * idempotency store nobody asked for. All of them are conditional on {@link RedisRuntimeOwner},
- * which exists only while the global switch is on — a role selector never activates Redis by
- * itself, and {@code RedisActivationValidator} refuses the contradiction of a role that selects
- * Redis while the switch is off.
- *
- *
The namespace comes from {@code app.redis.namespace} for every capability. That is the change
- * that makes the ACL pattern meaningful: one prefix, {@code {environment}:{service}:{domain}}, that
- * the deployment's {@code ~} pattern can actually fence.
- */
-@Configuration(proxyBeanMethods = false)
-@EnableConfigurationProperties(RedisCapabilitySettings.class)
-// The global switch, not a role selector. A role selector says which capability composes; it never
-// says Redis exists, and RedisActivationValidator refuses the contradiction of one without the
-// other.
-@ConditionalOnProperty(prefix = "app.redis", name = "enabled", havingValue = "true")
-public class RedisCapabilityConfig {
-
- /**
- * Bridges the deployment's configured secret backend into the Redis SDK.
- *
- *
Without it the SDK falls back to reading the process environment directly, which quietly
- * bypasses whichever backend {@code ca-skeleton.secret-source.strategy} selected — so a
- * deployment using a secret manager would resolve its Redis credential from an environment
- * variable that is not supposed to hold it.
- *
- * @param secretSource the configured backend
- * @return the SDK's view of it
- */
- @Bean
- RedisSdkAutoConfiguration.RedisSecretSource redisSdkSecretSource(SecretSource secretSource) {
- return secretSource::resolve;
- }
-
- private static RedisNamespace namespaceOf(RedisSdkSettings settings) {
- return new RedisNamespace(
- settings.getNamespace().getEnvironment(),
- settings.getNamespace().getService(),
- settings.getNamespace().getDomain());
- }
-
- /**
- * The semantic cache region for the default binding.
- *
- * @param owner the Redis runtime owner
- * @param sdk the Redis SDK settings, which own the namespace
- * @param capabilities the capability settings
- * @param secretSource resolves the key-digest material
- * @param clock the clock expiries are measured against
- * @return the cache region port
- */
- @Bean
- @ConditionalOnProperty(
- name = "ca-skeleton.capabilities.cache.bindings.default",
- havingValue = "redis")
- CacheRegionPort redisDefaultCacheRegion(
- RedisRuntimeOwner owner,
- RedisSdkSettings sdk,
- RedisCapabilitySettings capabilities,
- SecretSource secretSource,
- Clock clock) {
- RedisCapabilitySettings.Cache cache = capabilities.getCache();
- cache.validate();
- KeyDigest digest = KeyDigest.of(secretSource, cache.getKeyHmacSecretReference(), "cache", sdk);
- return new RedisCacheRegionAdapter<>(
- owner,
- new RedisCacheRegionAdapter.CacheKeys(
- namespaceOf(sdk), cache.getSemanticRegion(), cache.getKeyVersion()),
- digest::of,
- value -> value,
- stored -> stored,
- clock,
- cache.getPositiveSoftTtl(),
- cache.getPositiveHardTtl(),
- cache.getNegativeTtl(),
- cache.getCommandTimeout());
- }
-
- /**
- * The provider-neutral edge rate limiter, backed by Redis.
- *
- * @param owner the Redis runtime owner
- * @param sdk the Redis SDK settings, which own the namespace
- * @param capabilities the capability settings
- * @param clock the clock decision windows are measured against
- * @return the rate limit port
- */
- @Bean
- @ConditionalOnProperty(
- name = "ca-skeleton.capabilities.rate-limit.provider",
- havingValue = "redis")
- EdgeRateLimitPort redisEdgeRateLimitPort(
- RedisRuntimeOwner owner,
- RedisSdkSettings sdk,
- RedisCapabilitySettings capabilities,
- Clock clock) {
- RedisCapabilitySettings.RateLimit rateLimit = capabilities.getRateLimit();
- return new RedisEdgeRateLimitAdapter(
- owner,
- new RateLimitKeys(namespaceOf(sdk), rateLimit.getKeyVersion()),
- policiesOf(rateLimit),
- new RateLimitScripts(),
- clock,
- rateLimit.getCommandTimeout(),
- rateLimit.getFailureRetryAfter());
- }
-
- private static Map policiesOf(
- RedisCapabilitySettings.RateLimit rateLimit) {
- if (rateLimit.getPolicies().isEmpty()) {
- // An enabled limiter with no policy would answer every request "unknown policy", which the
- // port reports as a deployment error on every call rather than once at startup.
- throw new IllegalStateException(
- "the Redis rate limiter is selected but no policy is configured under"
- + " ca-skeleton.capabilities.rate-limit.policies");
- }
- Map policies = new LinkedHashMap<>();
- rateLimit
- .getPolicies()
- .forEach((id, policy) -> policies.put(id, policyOf(id, policy, rateLimit)));
- if (!policies.containsKey(rateLimit.getDefaultPolicyId())) {
- throw new IllegalStateException(
- "the default rate-limit policy '"
- + rateLimit.getDefaultPolicyId()
- + "' is not among the configured policies "
- + policies.keySet());
- }
- return policies;
- }
-
- private static RateLimitPolicy policyOf(
- String id,
- RedisCapabilitySettings.RateLimit.Policy policy,
- RedisCapabilitySettings.RateLimit rateLimit) {
- RateLimitAlgorithm algorithm =
- switch (policy.getAlgorithm().toLowerCase(Locale.ROOT)) {
- case "fixed-window" -> RateLimitAlgorithm.FIXED_WINDOW;
- case "sliding-counter" -> RateLimitAlgorithm.SLIDING_COUNTER;
- case "token-bucket" -> RateLimitAlgorithm.TOKEN_BUCKET;
- default ->
- throw new IllegalStateException(
- "unsupported rate-limit algorithm '" + policy.getAlgorithm() + "' for " + id);
- };
- RateParameters parameters =
- switch (algorithm) {
- case FIXED_WINDOW ->
- new RateParameters.FixedWindow(policy.getLimit(), policy.getWindow());
- case SLIDING_COUNTER ->
- new RateParameters.SlidingCounter(policy.getLimit(), policy.getWindow());
- case TOKEN_BUCKET ->
- new RateParameters.TokenBucket(
- policy.getCapacity(), policy.getRefillTokens(), policy.getRefillPeriod());
- default -> throw new IllegalStateException("unsupported rate-limit algorithm for " + id);
- };
- if (!"fail-closed".equalsIgnoreCase(rateLimit.getFailurePolicy())) {
- // The port only implements fail-closed, so a deployment that configured anything else has a
- // limit it believes is permissive and is not.
- throw new IllegalStateException(
- "only fail-closed is supported for the Redis rate limiter, but"
- + " ca-skeleton.capabilities.rate-limit.failure-policy is '"
- + rateLimit.getFailurePolicy()
- + "'");
- }
- return new RateLimitPolicy(
- id,
- policy.getRevision(),
- algorithm,
- parameters,
- policy.getMaximumCost(),
- policy.getCleanupGrace(),
- policy.getMaximumClockRegression(),
- RateLimitFailurePolicy.FAIL_CLOSED);
- }
-
- /**
- * The distributed lease port. Efficiency only — it never supplies fencing.
- *
- * @param owner the Redis runtime owner
- * @param sdk the Redis SDK settings, which own the namespace
- * @param capabilities the capability settings
- * @param clock the wall clock used for reporting instants
- * @return the lease port
- */
- @Bean
- @ConditionalOnProperty(name = "ca-skeleton.capabilities.lease.provider", havingValue = "redis")
- DistributedLeasePort redisDistributedLeasePort(
- RedisRuntimeOwner owner,
- RedisSdkSettings sdk,
- RedisCapabilitySettings capabilities,
- Clock clock) {
- RedisCapabilitySettings.Lease lease = capabilities.getLease();
- return new RedisDistributedLeaseAdapter(
- owner,
- new RedisDistributedLeaseAdapter.LeaseKeys(namespaceOf(sdk), lease.getKeyVersion()),
- new LeaseScripts(),
- clock,
- System::nanoTime,
- lease.getCommandTimeout(),
- lease.getContentionRetryAfter(),
- lease.getDriftBudget());
- }
-
- /**
- * The owner-safe request-replay store.
- *
- * @param owner the Redis runtime owner
- * @param sdk the Redis SDK settings, which own the namespace
- * @param capabilities the capability settings
- * @param clock the clock lease deadlines are measured against
- * @return the V2 store port
- */
- @Bean
- @ConditionalOnProperty(
- name = "ca-skeleton.capabilities.idempotency.provider",
- havingValue = "redis")
- IdempotencyStorePortV2 redisIdempotencyStore(
- RedisRuntimeOwner owner,
- RedisSdkSettings sdk,
- RedisCapabilitySettings capabilities,
- Clock clock) {
- RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency();
- return new RedisIdempotencyStoreAdapter(
- owner,
- new RedisIdempotencyStoreAdapter.IdempotencyKeys(
- namespaceOf(sdk), idempotency.getKeyVersion()),
- new IdempotencyScripts(),
- clock,
- idempotency.getCommandTimeout());
- }
-
- /**
- * The request-replay lifecycle driven over the selected owner-safe store.
- *
- *
Provider-neutral by type — it depends on {@link IdempotencyStorePortV2}, not on Redis — and
- * composed here because this is where the settings behind it are bound. A selection that produced
- * the store and no executor produced a store nothing could drive, which is what {@code
- * IdempotencyProviderSelectionConfig} refuses.
- *
- * @param store the selected owner-safe store
- * @param capabilities the capability settings
- * @return the owner-safe request-replay executor
- */
- @Bean
- @ConditionalOnProperty(
- name = "ca-skeleton.capabilities.idempotency.provider",
- havingValue = "redis")
- IdempotencyExecutorV2 idempotencyExecutorV2(
- IdempotencyStorePortV2 store, RedisCapabilitySettings capabilities) {
- RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency();
- return new IdempotencyExecutorV2(
- store,
- idempotency.getProcessingLease(),
- idempotency.getReplayTtl(),
- idempotency.getFailureRetention(),
- idempotency.getResponseCodecId(),
- idempotency.getPolicyRevision());
- }
-
- /**
- * Digests a semantic key before it reaches Redis.
- *
- *
Keyed, not plain. An unkeyed digest of a low-entropy identifier — a customer number, a short
- * order id — is reversible by anyone who can read the keyspace and can guess the format, which is
- * everyone who can run {@code SCAN}. The key comes from the deployment's secret backend, so the
- * keyspace is only correlatable by something that already holds the secret.
- */
- private record KeyDigest(javax.crypto.Mac prototype) {
-
- static KeyDigest of(
- SecretSource secretSource, String reference, String capability, RedisSdkSettings sdk) {
- String name = secretName(reference, capability);
- String material =
- secretSource
- .resolve(name)
- .orElseThrow(
- () ->
- new IllegalStateException(
- "the Redis "
- + capability
- + " key-digest secret '"
- + name
- + "' resolved to nothing. Without it the keyspace would carry"
- + " identifiers in a form anybody who can read it could reverse."));
- try {
- javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
- mac.init(
- new javax.crypto.spec.SecretKeySpec(
- // Namespace-bound: the same identifier in staging and production must not digest
- // to the same key, or a keyspace dump from the softer environment would map
- // one-for-one onto the harder one.
- (material
- + '|'
- + sdk.getNamespace().getEnvironment()
- + ':'
- + sdk.getNamespace().getService()
- + ':'
- + sdk.getNamespace().getDomain())
- .getBytes(StandardCharsets.UTF_8),
- "HmacSHA256"));
- return new KeyDigest(mac);
- } catch (java.security.GeneralSecurityException failure) {
- throw new IllegalStateException("HmacSHA256 must be available", failure);
- }
- }
-
- private static String secretName(String reference, String capability) {
- if (reference == null || reference.isBlank()) {
- throw new IllegalStateException(
- "the Redis " + capability + " capability needs a key-digest secret reference");
- }
- int separator = reference.lastIndexOf('/');
- if (!reference.startsWith("secret://") || separator < 0) {
- throw new IllegalStateException(
- "the Redis "
- + capability
- + " key-digest reference '"
- + reference
- + "' must be secret:///");
- }
- return reference.substring(separator + 1);
- }
-
- String of(String key) {
- try {
- javax.crypto.Mac mac = (javax.crypto.Mac) prototype.clone();
- return "hv1:"
- + java.util.HexFormat.of().formatHex(mac.doFinal(key.getBytes(StandardCharsets.UTF_8)));
- } catch (CloneNotSupportedException failure) {
- throw new IllegalStateException("a Mac must be cloneable", failure);
- }
- }
- }
-}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java
index b8393da0..ebcde5fe 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java
@@ -1,9 +1,9 @@
package dev.caskeleton.bootstrap.runtime;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.bootstrap.runtime.startup.StartupPhase;
+import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext;
import java.time.temporal.ChronoUnit;
-import java.util.ArrayList;
-import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.convert.DurationStyle;
import org.springframework.core.env.Environment;
@@ -35,7 +35,8 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton
@Override
public void afterSingletonsInstantiated() {
- List violations = new ArrayList<>();
+ StartupValidationContext violations =
+ StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION);
Long connectionTimeout =
parseMillis(CONNECTION_TIMEOUT_KEY, environment.getProperty(CONNECTION_TIMEOUT_KEY));
@@ -48,7 +49,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton
parseMillis(LEAK_DETECTION_KEY, environment.getProperty(LEAK_DETECTION_KEY));
if (connectionTimeout != null && connectionTimeout < 250L) {
- violations.add(
+ violations.reject(
"D2/HIKARI-CFG-C1: "
+ CONNECTION_TIMEOUT_ENV_KEY
+ " ("
@@ -60,7 +61,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton
if (validationTimeout != null
&& connectionTimeout != null
&& validationTimeout >= connectionTimeout) {
- violations.add(
+ violations.reject(
"D7/HIKARI-CFG-C6: validation-timeout ("
+ VALIDATION_TIMEOUT_KEY
+ ", "
@@ -76,7 +77,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton
}
if (keepaliveTime != null && maxLifetime != null && keepaliveTime >= maxLifetime) {
- violations.add(
+ violations.reject(
"D4/HIKARI-CFG-C4: keepalive-time ("
+ KEEPALIVE_TIME_KEY
+ ", "
@@ -92,7 +93,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton
}
if (leakDetection != null && leakDetection != 0L && leakDetection < 2000L) {
- violations.add(
+ violations.reject(
"D5/HIKARI-CFG-C5: leak-detection-threshold ("
+ LEAK_DETECTION_KEY
+ ", "
@@ -102,12 +103,8 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton
+ leakDetection);
}
- if (!violations.isEmpty()) {
- throw StartupFailures.envValidation(
- "invalid HikariCP configuration (feature-database-connection-pool-contract"
- + " D2/D4/D5/D7): "
- + violations);
- }
+ violations.failIfViolated(
+ "invalid HikariCP configuration (feature-database-connection-pool-contract D2/D4/D5/D7)");
}
/**
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java
index 6543460b..44310479 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java
@@ -1,6 +1,8 @@
package dev.caskeleton.bootstrap.runtime;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
+import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.springframework.beans.factory.SmartInitializingSingleton;
@@ -15,7 +17,6 @@ public class JpaSchemaSafetyValidator implements SmartInitializingSingleton {
static final String DDL_AUTO_KEY = "spring.jpa.hibernate.ddl-auto";
static final String DDL_AUTO_ENV_KEY = "APP_DATASOURCE_DDL_AUTO";
- private static final String PROD_PROFILE = "prod";
private static final Set PROD_ALLOWED_MODES = Set.of("none", "validate");
private final Environment environment;
@@ -49,11 +50,6 @@ public class JpaSchemaSafetyValidator implements SmartInitializingSingleton {
}
private boolean isProdActive() {
- for (String profile : environment.getActiveProfiles()) {
- if (PROD_PROFILE.equalsIgnoreCase(profile)) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java
index 84b9c3ca..42ac0d6c 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java
@@ -1,6 +1,8 @@
package dev.caskeleton.bootstrap.runtime;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
+import java.util.List;
import java.util.Locale;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.env.Environment;
@@ -35,7 +37,6 @@ public final class PersistenceVendorProdSafetyValidator implements SmartInitiali
static final String JDBC_URL_KEY = "spring.datasource.url";
static final String JDBC_URL_ENV_KEY = "APP_DATASOURCE_URL";
- private static final String PROD_PROFILE = "prod";
private static final String H2_VENDOR = "h2";
private static final String H2_URL_PREFIX = "jdbc:h2:";
@@ -79,11 +80,6 @@ public final class PersistenceVendorProdSafetyValidator implements SmartInitiali
}
private boolean isProdActive() {
- for (String profile : environment.getActiveProfiles()) {
- if (PROD_PROFILE.equalsIgnoreCase(profile)) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java
index 1d5463ce..349ec989 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java
@@ -1,6 +1,7 @@
package dev.caskeleton.bootstrap.runtime;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -22,7 +23,6 @@ public final class PostgreSqlTransportSecurityValidator implements SmartInitiali
static final String HIKARI_SSLMODE_KEY =
"spring.datasource.hikari.data-source-properties.sslmode";
- private static final String PROD_PROFILE = "prod";
private static final String POSTGRESQL_PREFIX = "jdbc:postgresql:";
private static final String VERIFY_FULL = "verify-full";
private static final Pattern URL_SSLMODE = Pattern.compile("(?i)(?:[?&])sslmode=([^&]*)");
@@ -84,11 +84,6 @@ public final class PostgreSqlTransportSecurityValidator implements SmartInitiali
}
private boolean isProdActive() {
- for (String profile : environment.getActiveProfiles()) {
- if (PROD_PROFILE.equalsIgnoreCase(profile)) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java
new file mode 100644
index 00000000..08f81472
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java
@@ -0,0 +1,66 @@
+package dev.caskeleton.bootstrap.runtime;
+
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+
+/**
+ * The readiness health group, and the one way a capability adds itself to it.
+ *
+ *
Two capability post-processors — Redis and the datasource — each contributed a member by
+ * reading {@code management.endpoint.health.group.readiness.include}, splitting it on commas,
+ * trimming, adding their own contributor, and pushing a new property source to the front. The code
+ * was the same in both, and the property key itself was declared in the Redis one and reached from
+ * the database one, so a package move turned a shared contract into a compile error and revealed
+ * that Redis had never owned the key at all — it had simply declared it first.
+ *
+ *
The key belongs to the readiness group, so it lives here, and so does the merge. What stays
+ * with each capability is the only part that is actually theirs: whether they are active, and which
+ * contributor names them.
+ */
+public final class ReadinessGroup {
+
+ /** The Spring property naming the health contributors the readiness probe aggregates. */
+ public static final String INCLUDE_PROPERTY =
+ "management.endpoint.health.group.readiness.include";
+
+ private ReadinessGroup() {}
+
+ /**
+ * Adds a contributor to the readiness group unless it is already named.
+ *
+ *
Returning without contributing when the member is already present is deliberate and not
+ * merely an optimisation: pushing a source that repeats what the operator wrote would be harmless
+ * at runtime and would hide, in {@code /actuator/configprops}, that they had asked for it
+ * themselves.
+ *
+ *
Contributions chain rather than race because the callers declare distinct orders in {@link
+ * dev.caskeleton.bootstrap.runtime.startup.StartupOrder}; each one reads what the previous
+ * contributed.
+ *
+ * @param environment the environment to contribute to
+ * @param propertySourceName a name unique to the contributing capability, so the source is
+ * identifiable in {@code /actuator/env}
+ * @param member the health contributor to include
+ */
+ public static void include(
+ ConfigurableEnvironment environment, String propertySourceName, String member) {
+ String configured = environment.getProperty(INCLUDE_PROPERTY, "");
+ Set members = new LinkedHashSet<>();
+ Arrays.stream(configured.split(","))
+ .map(String::trim)
+ .filter(existing -> !existing.isEmpty())
+ .forEach(members::add);
+ if (!members.add(member)) {
+ return;
+ }
+ environment
+ .getPropertySources()
+ .addFirst(
+ new MapPropertySource(
+ propertySourceName, Map.of(INCLUDE_PROPERTY, String.join(",", members))));
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java
index 05fd7518..e0ac185e 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java
@@ -1,7 +1,7 @@
package dev.caskeleton.bootstrap.runtime;
-import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
-import java.util.ArrayList;
+import dev.caskeleton.bootstrap.runtime.startup.StartupPhase;
+import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext;
import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.env.Environment;
@@ -35,14 +35,15 @@ public class RuntimeNumericBoundsValidator implements SmartInitializingSingleton
@Override
public void afterSingletonsInstantiated() {
- List violations = new ArrayList<>();
+ StartupValidationContext violations =
+ StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION);
for (Bound bound : BOUNDS) {
Integer value = environment.getProperty(bound.springKey(), Integer.class);
if (value == null) {
continue; // absent → skip (framework default owns it)
}
if (value < bound.minInclusive()) {
- violations.add(
+ violations.reject(
bound.envKey()
+ " ("
+ bound.springKey()
@@ -52,11 +53,8 @@ public class RuntimeNumericBoundsValidator implements SmartInitializingSingleton
+ value);
}
}
- if (!violations.isEmpty()) {
- throw StartupFailures.envValidation(
- "invalid runtime numeric configuration (feature-env-driven-runtime-configuration "
- + "D10 — no lenient default): "
- + violations);
- }
+ violations.failIfViolated(
+ "invalid runtime numeric configuration (feature-env-driven-runtime-configuration "
+ + "D10 — no lenient default)");
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java
new file mode 100644
index 00000000..b02de0ea
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java
@@ -0,0 +1,85 @@
+package dev.caskeleton.bootstrap.runtime;
+
+import static net.logstash.logback.argument.StructuredArguments.kv;
+import static net.logstash.logback.argument.StructuredArguments.v;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Single source for the two warnings this composition root actually emits.
+ *
+ *
{@code StartupFailures} already did this for the failures — one place, fixed structured
+ * fields, an operator who can filter on {@code error.code}. Nothing did it for the warnings, so
+ * seventeen {@code log.warn} calls each invented their own sentence and none of them carried a
+ * field (BOOT-009). Grepping "how often does a meter fail to record?" across a fleet meant matching
+ * prose.
+ *
+ *
Both methods below are frames, not wrappers: the fixed part is the field set, and the varying
+ * part is passed in. That is the distinction the review asked for — keep what must differ, fix what
+ * must not.
+ *
+ *
Why these two and no more
+ *
+ *
A warning is not a failure. Everything here describes a runtime that is continuing: a meter
+ * that could not be recorded, a setting that was replaced by its default. Anything that should stop
+ * the process belongs in {@code StartupFailures} instead, and inventing a third frame before a
+ * third shape exists would be the speculative generality this repository's own review argued
+ * against.
+ */
+public final class RuntimeWarnings {
+
+ private static final Logger log = LoggerFactory.getLogger("dev.caskeleton.bootstrap.runtime");
+
+ private RuntimeWarnings() {}
+
+ /**
+ * A meter could not be registered, recorded or refreshed.
+ *
+ *
Observability failing is never a reason to fail the operation being observed, so every
+ * caller of this swallows the cause and continues. That is exactly why the warning has to be
+ * findable: it is the only trace that a metric an operator is watching has silently stopped
+ * moving.
+ *
+ * @param capability the capability whose meter failed — {@code outbox}, {@code distributed-lock},
+ * {@code tracing}, {@code background-job}
+ * @param meter the meter name or the operation that failed
+ * @param cause the failure
+ */
+ public static void meterUnavailable(String capability, String meter, Throwable cause) {
+ // v(), not kv(): both attach the field, but kv() also renders "name=value" into the message
+ // text, which reads as noise in a plain-text console and duplicates what the JSON encoder
+ // already emits. The rendered sentence stays a sentence; the fields are still there.
+ log.warn(
+ "{} metrics: {} could not be recorded",
+ v("capability", capability),
+ v("meter", meter),
+ kv("warning.kind", "meter-unavailable"),
+ cause);
+ }
+
+ /**
+ * A configured value was rejected and its default applied instead.
+ *
+ *
Deliberately a warning rather than a startup failure: these settings shape logging, and
+ * refusing to start because a log rotation count was zero would make the observability
+ * configuration able to take the application down. The trade is that the deployment runs with a
+ * value nobody asked for, so the message names the key, what was rejected and what is in force —
+ * an operator reading only the applied value would never know their configuration was ignored.
+ *
+ * @param environmentKey the environment variable an operator would edit
+ * @param rejected the configured value that was refused; may be {@code null} or blank
+ * @param applied the value actually in force
+ * @param reason why the configured value was refused
+ */
+ public static void settingFallback(
+ String environmentKey, Object rejected, Object applied, String reason) {
+ log.warn(
+ "{} was rejected and its default applied: {} (rejected '{}', in force '{}')",
+ v("env.key", environmentKey),
+ reason,
+ v("setting.rejected", String.valueOf(rejected)),
+ v("setting.applied", String.valueOf(applied)),
+ kv("warning.kind", "setting-fallback"));
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java
index 2a037024..12f49578 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.runtime;
+import dev.caskeleton.bootstrap.autoconfigure.redis.RedisActivationValidator;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java
index f955e4e6..640ac834 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java
@@ -1,6 +1,7 @@
package dev.caskeleton.bootstrap.runtime;
import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
@@ -26,8 +27,6 @@ public class SecretSourceValidator implements SmartInitializingSingleton {
*/
public static final String REDIS_ENABLED_PROPERTY = "app.redis.enabled";
- private static final String PROD_PROFILE = "prod";
-
/**
* Secret keys that must be injected under the {@code prod} profile. {@code
* SecretsClassificationRegistryTest} asserts this list matches the registry 1:1. See README for
@@ -171,12 +170,6 @@ public class SecretSourceValidator implements SmartInitializingSingleton {
}
private boolean isProdActive() {
- // Case-insensitive: a typo such as SPRING_PROFILES_ACTIVE=PROD must still match.
- for (String profile : environment.getActiveProfiles()) {
- if (PROD_PROFILE.equalsIgnoreCase(profile)) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java
index c89a5531..49ae8023 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java
@@ -1,8 +1,9 @@
package dev.caskeleton.bootstrap.runtime;
-import dev.caskeleton.bootstrap.runtime.startup.StartupFailures;
+import dev.caskeleton.bootstrap.runtime.startup.StartupPhase;
+import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext;
import dev.caskeleton.bootstrap.settings.RuntimeSafetySettings;
-import java.util.ArrayList;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.util.List;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
@@ -29,8 +30,6 @@ public class StartupSafetyValidator implements SmartInitializingSingleton {
"migrationStartupRunner" // platform migration startup job
);
- private static final String PROD_PROFILE = "prod";
-
private final Environment environment;
private final RuntimeSafetySettings settings;
private final ListableBeanFactory beanFactory;
@@ -53,20 +52,18 @@ public class StartupSafetyValidator implements SmartInitializingSingleton {
if (!isProdActive()) {
return;
}
- List unsafe = new ArrayList<>();
+ StartupValidationContext unsafe = StartupValidationContext.forPhase(StartupPhase.PROFILE_CHECK);
if (settings.errorDetailExposureEnabled()) {
- unsafe.add("APP_ERROR_DETAIL_EXPOSURE_ENABLED");
+ unsafe.reject("APP_ERROR_DETAIL_EXPOSURE_ENABLED");
}
if (settings.logBodyCaptureEnabled()) {
- unsafe.add("APP_LOG_BODY_CAPTURE_ENABLED");
- }
- if (!unsafe.isEmpty()) {
- // prod-unsafe toggle left on under prod → PROFILE_MISMATCH (exit 71).
- throw StartupFailures.profileMismatch(
- "prod profile forbids these toggles being enabled: "
- + unsafe
- + " — set them to false for the prod profile.");
+ unsafe.reject("APP_LOG_BODY_CAPTURE_ENABLED");
}
+ // The phase the context was opened for is what selects PROFILE_MISMATCH (exit 71); the
+ // classification is no longer restated at the throw site.
+ unsafe.failIfViolated(
+ "prod profile forbids these toggles being enabled",
+ "set them to false for the prod profile.");
}
/** When multi-instance is on, every instance-coordination capability bean must exist. */
@@ -74,30 +71,21 @@ public class StartupSafetyValidator implements SmartInitializingSingleton {
if (!settings.multiInstanceEnabled()) {
return;
}
- List missing = new ArrayList<>();
+ StartupValidationContext missing =
+ StartupValidationContext.forPhase(StartupPhase.ADAPTER_ENABLEMENT);
for (String beanName : REQUIRED_MULTI_INSTANCE_BEANS) {
if (!beanFactory.containsBean(beanName)) {
- missing.add(beanName);
+ missing.reject(beanName);
}
}
- if (!missing.isEmpty()) {
- // required coordination bean absent under multi-instance → REQUIRED_ADAPTER_DISABLED (exit
- // 72).
- throw StartupFailures.requiredAdapterDisabled(
- "APP_MULTI_INSTANCE_ENABLED=true requires the instance-coordination beans "
- + REQUIRED_MULTI_INSTANCE_BEANS
- + ", but these are missing: "
- + missing);
- }
+ // ADAPTER_ENABLEMENT (exit 72) follows from the context's phase.
+ missing.failIfViolated(
+ "APP_MULTI_INSTANCE_ENABLED=true requires the instance-coordination beans "
+ + REQUIRED_MULTI_INSTANCE_BEANS
+ + ", but these are missing");
}
private boolean isProdActive() {
- // Case-insensitive: a typo such as SPRING_PROFILES_ACTIVE=PROD must still match.
- for (String profile : environment.getActiveProfiles()) {
- if (PROD_PROFILE.equalsIgnoreCase(profile)) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java
index eea2bc1d..c46959a2 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java
@@ -1,6 +1,6 @@
package dev.caskeleton.bootstrap.runtime.startup;
-import java.util.ArrayList;
+import dev.caskeleton.shared.runtime.RuntimeEnvironment;
import java.util.List;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.env.Environment;
@@ -15,8 +15,6 @@ public class FlywayProdSafetyValidator implements SmartInitializingSingleton {
static final String OUT_OF_ORDER_KEY = "spring.flyway.out-of-order";
static final String CLEAN_DISABLED_KEY = "spring.flyway.clean-disabled";
- private static final String PROD_PROFILE = "prod";
-
private final Environment environment;
public FlywayProdSafetyValidator(Environment environment) {
@@ -28,24 +26,22 @@ public class FlywayProdSafetyValidator implements SmartInitializingSingleton {
if (!isProdActive()) {
return;
}
- List violations = new ArrayList<>();
+ StartupValidationContext violations =
+ StartupValidationContext.forPhase(StartupPhase.PROFILE_CHECK);
if (isTrue(BASELINE_ON_MIGRATE_KEY)) {
- violations.add(BASELINE_ON_MIGRATE_KEY + "=true (removes the missing-migration safety net)");
+ violations.reject(
+ BASELINE_ON_MIGRATE_KEY + "=true (removes the missing-migration safety net)");
}
if (isTrue(OUT_OF_ORDER_KEY)) {
- violations.add(OUT_OF_ORDER_KEY + "=true (breaks migration ordering consistency)");
+ violations.reject(OUT_OF_ORDER_KEY + "=true (breaks migration ordering consistency)");
}
if (isFalse(CLEAN_DISABLED_KEY)) {
- violations.add(CLEAN_DISABLED_KEY + "=false (re-arms destructive Flyway clean)");
- }
- if (!violations.isEmpty()) {
- throw StartupFailures.profileMismatch(
- "prod profile forbids these Flyway options (feature-migration-startup-contract"
- + " D2/D4): "
- + violations
- + " — partial-schema recovery goes through runbook://migration/manual-recovery,"
- + " never an in-prod Flyway repair.");
+ violations.reject(CLEAN_DISABLED_KEY + "=false (re-arms destructive Flyway clean)");
}
+ violations.failIfViolated(
+ "prod profile forbids these Flyway options (feature-migration-startup-contract D2/D4)",
+ "partial-schema recovery goes through runbook://migration/manual-recovery, never an"
+ + " in-prod Flyway repair.");
}
private boolean isTrue(String key) {
@@ -58,12 +54,6 @@ public class FlywayProdSafetyValidator implements SmartInitializingSingleton {
}
private boolean isProdActive() {
- // Case-insensitive so a SPRING_PROFILES_ACTIVE=PROD typo still triggers the guard.
- for (String profile : environment.getActiveProfiles()) {
- if (PROD_PROFILE.equalsIgnoreCase(profile)) {
- return true;
- }
- }
- return false;
+ return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()));
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java
index be460938..bb48f8d8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java
@@ -1,7 +1,5 @@
package dev.caskeleton.bootstrap.runtime.startup;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.core.env.Environment;
@@ -28,7 +26,8 @@ public class RequiredEnvironmentValidator implements SmartInitializingSingleton
@Override
public void afterSingletonsInstantiated() {
- List missing = new ArrayList<>();
+ StartupValidationContext missing =
+ StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION);
// Sorted for a stable, deterministic message regardless of map iteration order.
REQUIRED_DATASOURCE_ENV.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
@@ -36,14 +35,11 @@ public class RequiredEnvironmentValidator implements SmartInitializingSingleton
entry -> {
String value = environment.getProperty(entry.getKey());
if (value == null || value.isBlank()) {
- missing.add(entry.getValue() + " (" + entry.getKey() + ")");
+ missing.reject(entry.getValue() + " (" + entry.getKey() + ")");
}
});
- if (!missing.isEmpty()) {
- throw StartupFailures.envValidation(
- "required datasource environment is missing or blank — a Flyway migration"
- + " cannot run without it: "
- + missing);
- }
+ missing.failIfViolated(
+ "required datasource environment is missing or blank — a Flyway migration"
+ + " cannot run without it");
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java
index 655c9e1d..d9869e89 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java
@@ -18,8 +18,19 @@ public final class StartupFailures {
private StartupFailures() {}
public static StartupValidationException envValidation(String detail) {
- emit(StartupErrorCode.STARTUP_VALIDATION_FAILED, detail, null);
- return new StartupValidationException(detail);
+ return envValidation(detail, null);
+ }
+
+ /**
+ * Raises an environment/configuration validation failure that a lower-level exception proved.
+ *
+ * @param detail the operator-facing detail
+ * @param cause the failure that proved the configuration wrong; may be {@code null}
+ * @return the exception to throw
+ */
+ public static StartupValidationException envValidation(String detail, Throwable cause) {
+ emit(StartupErrorCode.STARTUP_VALIDATION_FAILED, detail, cause);
+ return new StartupValidationException(detail, cause);
}
public static ProfileMismatchException profileMismatch(String detail) {
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java
new file mode 100644
index 00000000..19afd86a
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java
@@ -0,0 +1,66 @@
+package dev.caskeleton.bootstrap.runtime.startup;
+
+import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
+import org.springframework.core.Ordered;
+
+/**
+ * The order every {@code EnvironmentPostProcessor} in this composition root runs in, in one place.
+ *
+ *
Six post-processors are registered in {@code META-INF/spring.factories} and their relative
+ * order is part of how they work — a capability violation reported against a key the master switch
+ * was about to rewrite sends an operator somewhere useless. Three of them nonetheless returned the
+ * same value, {@code Ordered.LOWEST_PRECEDENCE}, and a fourth declared no order at all (BOOT-005).
+ * The sequence held only because Spring sorts stably and {@code spring.factories} happened to list
+ * them in the intended order: reordering four lines in a text file would have silently changed
+ * behaviour, and nothing would have failed.
+ *
+ *
{@link dev.caskeleton.bootstrap.activation.CapabilityDependencyEnvironmentValidator} even
+ * documented the constraint in its {@code getOrder()} Javadoc — "after {@code
+ * MasterSwitchEnvironmentPostProcessor} has rejected legacy switch spellings" — while returning a
+ * value that could not express it. A comment stating an ordering its return value does not enforce
+ * is the shape this class exists to remove.
+ *
+ *
Two bands, and why
+ *
+ *
The readiness post-processors contribute property sources and run immediately after config
+ * data, so they are expressed relative to {@link ConfigDataEnvironmentPostProcessor#ORDER}. The
+ * validators must observe the environment the application will actually run with — every
+ * contributed source included — so they stay at the {@code LOWEST_PRECEDENCE} end and are separated
+ * from each other by single steps. Keeping the last of them at exactly {@code LOWEST_PRECEDENCE}
+ * preserves this repository's existing position relative to any third-party post-processor at that
+ * value, so this change makes an undefined order deterministic without moving the whole band.
+ */
+public final class StartupOrder {
+
+ private StartupOrder() {}
+
+ /** Redis readiness group contribution — a property source, immediately after config data. */
+ public static final int READINESS_REDIS = ConfigDataEnvironmentPostProcessor.ORDER + 1;
+
+ /** Database readiness group contribution — a property source, immediately after config data. */
+ public static final int READINESS_DATABASE = ConfigDataEnvironmentPostProcessor.ORDER + 2;
+
+ /**
+ * Master-switch normalisation. First of the validators, because it rewrites and rejects legacy
+ * switch spellings; every rule after it reads the keys an operator will actually be told about.
+ */
+ public static final int MASTER_SWITCH_NORMALISATION = Ordered.LOWEST_PRECEDENCE - 3;
+
+ /**
+ * Runtime environment profile rule. After master-switch normalisation and before capability
+ * checks: which environment this is decides which capability combinations are even legal.
+ */
+ public static final int RUNTIME_ENVIRONMENT_PROFILE = Ordered.LOWEST_PRECEDENCE - 2;
+
+ /**
+ * Capability dependency graph. Last of the fail-closed rules, so its message names post-migration
+ * keys under a runtime environment that has already been established as valid.
+ */
+ public static final int CAPABILITY_DEPENDENCY = Ordered.LOWEST_PRECEDENCE - 1;
+
+ /**
+ * Tracing sample-rate defaulting. Runs after every rule that can refuse the boot outright — there
+ * is no value in defaulting a sample rate for a process that is about to exit.
+ */
+ public static final int TRACING_SAMPLING = Ordered.LOWEST_PRECEDENCE;
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java
new file mode 100644
index 00000000..db917fab
--- /dev/null
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java
@@ -0,0 +1,173 @@
+package dev.caskeleton.bootstrap.runtime.startup;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Collects every violation a startup guard finds, then raises them as one typed failure.
+ *
+ *
Fifteen guards in this leaf wrote the same shape by hand: a {@code new ArrayList<>()}, a
+ * series of {@code if (...) list.add(...)}, and a closing {@code if (!list.isEmpty()) throw ...}
+ * (BOOT-004). They did not even agree on what to call it — {@code violations}, {@code missing},
+ * {@code offenders}, {@code contradictions}, {@code unsafe}, {@code reasons}, {@code modes} — so
+ * the fact that they were one pattern was invisible in the code, and each copy independently chose
+ * how to join the messages and which exception to throw.
+ *
+ *
Accumulate, do not fail fast
+ *
+ *
Collecting every violation before failing is the point, and it is worth stating because the
+ * opposite is the more common instinct. An operator fixing a misconfigured deployment works in
+ * cycles of edit, restart, read the error. Reporting one problem at a time turns a single bad
+ * configuration into as many restarts as it has mistakes; reporting all of them costs nothing and
+ * ends the loop in one pass. This is also why Bean Validation's {@code @GroupSequence} was the
+ * wrong instrument here — group sequencing short-circuits, which is precisely the behaviour that
+ * must not happen inside one phase.
+ *
+ *
The phase chooses the exception
+ *
+ *
A context is opened for a {@link StartupPhase}, and the phase decides which {@link
+ * StartupFailures} factory raises the result — so the exit code, the {@code error.code} field and
+ * the {@code startup.phase} log field follow from one declaration at the top of the guard instead
+ * of being restated at the throw site. Before this, {@code StartupPhase} existed but was only ever
+ * written into a log message; nothing read it, and nothing derived behaviour from it (BOOT-005).
+ */
+public final class StartupValidationContext {
+
+ private final StartupPhase phase;
+ private final List violations = new ArrayList<>();
+
+ private StartupValidationContext(StartupPhase phase) {
+ this.phase = Objects.requireNonNull(phase, "phase");
+ }
+
+ /**
+ * Opens a context for the given phase.
+ *
+ *
{@link StartupPhase#MIGRATION} is refused here rather than at the failure site. A migration
+ * failure always carries the Flyway exception that caused it and is raised through {@link
+ * StartupFailures#migrationFailed}; a context cannot invent that cause. Rejecting the phase at
+ * construction puts the error where the mistake is — the guard that opened the wrong context —
+ * instead of surfacing it only on the day something actually goes wrong.
+ *
+ * @param phase the startup phase whose rules this context collects
+ * @return a new, empty context
+ * @throws IllegalArgumentException when the phase is {@code MIGRATION}
+ */
+ public static StartupValidationContext forPhase(StartupPhase phase) {
+ if (phase == StartupPhase.MIGRATION) {
+ throw new IllegalArgumentException(
+ "StartupPhase.MIGRATION cannot be accumulated: a migration failure carries the exception "
+ + "that caused it and is raised through StartupFailures.migrationFailed(detail, cause)");
+ }
+ return new StartupValidationContext(phase);
+ }
+
+ /** The phase this context collects for. */
+ public StartupPhase phase() {
+ return phase;
+ }
+
+ /**
+ * Records a violation unless the condition holds.
+ *
+ * @param satisfied the rule's outcome — {@code true} means nothing to record
+ * @param violation the operator-facing description of what is wrong, evaluated eagerly
+ * @return this context, so rules can be chained
+ */
+ public StartupValidationContext require(boolean satisfied, String violation) {
+ if (!satisfied) {
+ reject(violation);
+ }
+ return this;
+ }
+
+ /**
+ * Records a violation unconditionally.
+ *
+ * @param violation the operator-facing description of what is wrong
+ * @return this context, so rules can be chained
+ */
+ public StartupValidationContext reject(String violation) {
+ violations.add(Objects.requireNonNull(violation, "violation"));
+ return this;
+ }
+
+ /**
+ * Records every given violation.
+ *
+ * @param found violations produced elsewhere — a shared rule evaluated by another class
+ * @return this context, so rules can be chained
+ */
+ public StartupValidationContext rejectAll(List found) {
+ for (String violation : found) {
+ reject(violation);
+ }
+ return this;
+ }
+
+ /**
+ * Sorts the collected violations so the message is the same on every host.
+ *
+ *
For a guard that walks a {@code Map}, insertion order is iteration order and iteration order
+ * is not a contract — the same misconfiguration would otherwise produce a different message from
+ * one JVM to the next, which is exactly the kind of difference that makes an operator wonder
+ * whether they are looking at two problems.
+ *
+ * @return this context, so rules can be chained
+ */
+ public StartupValidationContext sorted() {
+ violations.sort(String::compareTo);
+ return this;
+ }
+
+ /** Reports whether anything was recorded. */
+ public boolean hasViolations() {
+ return !violations.isEmpty();
+ }
+
+ /** Everything recorded so far, in the order it was found. */
+ public List violations() {
+ return List.copyOf(violations);
+ }
+
+ /**
+ * Raises the collected violations as the failure this phase corresponds to, or returns quietly.
+ *
+ *
The summary is what an operator reads first, so it should name the thing being validated
+ * rather than repeat that validation failed; the violations follow it, semicolon-separated.
+ *
+ * @param summary a short lead-in naming what was being validated
+ */
+ public void failIfViolated(String summary) {
+ failIfViolated(summary, null);
+ }
+
+ /**
+ * Raises the collected violations, followed by what the operator should do about them.
+ *
+ *
The remedy is a separate parameter rather than part of the summary because it belongs after
+ * the list: "these options are forbidden: a; b — recovery goes through runbook://…" reads as
+ * instructions, while the same sentence before the list reads as a description of the list.
+ *
+ * @param summary a short lead-in naming what was being validated
+ * @param remedy what to do instead; may be {@code null}
+ */
+ public void failIfViolated(String summary, String remedy) {
+ if (violations.isEmpty()) {
+ return;
+ }
+ String detail =
+ summary + ": " + String.join("; ", violations) + (remedy == null ? "" : " — " + remedy);
+ throw switch (phase) {
+ case ENV_VALIDATION -> StartupFailures.envValidation(detail);
+ case PROFILE_CHECK -> StartupFailures.profileMismatch(detail);
+ case ADAPTER_ENABLEMENT -> StartupFailures.requiredAdapterDisabled(detail);
+ // Unreachable: forPhase refuses MIGRATION. The arm exists so the switch stays exhaustive
+ // without a `default`, which is what makes the compiler object if a fifth phase is added
+ // without deciding how it fails.
+ case MIGRATION ->
+ throw new AssertionError("forPhase rejects MIGRATION; collected: " + detail);
+ };
+ }
+}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java
index cfbfcee4..91bad398 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java
@@ -9,6 +9,21 @@ public class StartupValidationException extends StartupFailureException {
private static final long serialVersionUID = 1L;
public StartupValidationException(String message) {
- super(StartupErrorCode.STARTUP_VALIDATION_FAILED, message, null);
+ this(message, null);
+ }
+
+ /**
+ * Carries the underlying failure alongside the message.
+ *
+ *
Some configuration is only provably wrong by trying it — a datasource URL is valid text
+ * until a connection attempt refuses it. Those checks hold a {@code SQLException} whose message
+ * names the host, the port and the refusal, and dropping it would leave an operator with "could
+ * not open a connection" and nothing to act on.
+ *
+ * @param message the operator-facing detail
+ * @param cause the failure that proved the configuration wrong; may be {@code null}
+ */
+ public StartupValidationException(String message, Throwable cause) {
+ super(StartupErrorCode.STARTUP_VALIDATION_FAILED, message, cause);
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java
index 784c939c..c3c6bd60 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java
@@ -1,7 +1,7 @@
package dev.caskeleton.bootstrap.security;
-import java.util.ArrayList;
-import java.util.List;
+import dev.caskeleton.bootstrap.runtime.startup.StartupPhase;
+import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -21,28 +21,14 @@ public class AuthenticationModeCompositionConfig {
static void validate(
AuthenticationModeSettings.AuthenticationMode mode, ListableBeanFactory beans) {
- List contradictions = new ArrayList<>();
- boolean jwtDecoder = beans.containsBean("jwtDecoder");
- boolean sessionRepository = beans.containsBean("redisVersionedSessionRepository");
- boolean sessionFilter = beans.containsBean("springSessionRepositoryFilter");
- if (mode == AuthenticationModeSettings.AuthenticationMode.JWT) {
- if (!jwtDecoder) {
- contradictions.add("jwtDecoder is absent");
- }
- if (sessionRepository || sessionFilter) {
- contradictions.add("Redis Session repository/filter is active");
- }
- } else {
- if (jwtDecoder) {
- contradictions.add("jwtDecoder is active");
- }
- if (!sessionRepository || !sessionFilter) {
- contradictions.add("Redis Session repository/filter is incomplete");
- }
+ StartupValidationContext contradictions =
+ StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION);
+ for (String required : mode.requiredBeans()) {
+ contradictions.require(beans.containsBean(required), required + " is absent");
}
- if (!contradictions.isEmpty()) {
- throw new IllegalStateException(
- "Authentication mode composition is not exclusive for " + mode + ": " + contradictions);
+ for (String forbidden : mode.forbiddenBeans()) {
+ contradictions.require(!beans.containsBean(forbidden), forbidden + " is active");
}
+ contradictions.failIfViolated("Authentication mode composition is not exclusive for " + mode);
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java
index 40a72d06..b0ca3548 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java
@@ -1,5 +1,6 @@
package dev.caskeleton.bootstrap.security;
+import java.util.Set;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.ConstructorBinding;
@@ -12,8 +13,56 @@ public record AuthenticationModeSettings(AuthenticationMode authMode) {
this.authMode = authMode == null ? AuthenticationMode.JWT : authMode;
}
+ /**
+ * The exclusive authentication modes, each carrying the beans it requires and forbids.
+ *
+ *
The composition guard used to decide this with an {@code if (mode == JWT) … else …}, so a
+ * third mode would have taken the {@code else} branch and been validated as though it were Redis
+ * Session — a wrong answer rather than a refusal (BOOT-010). Declaring the two bean sets on the
+ * constant means a third mode cannot be added without naming them: the abstract methods below
+ * make the compiler ask.
+ *
+ *
Constant bodies rather than constructor arguments, and that is not a style preference. Error
+ * Prone's {@code ImmutableEnumChecker} rejects a {@code Set} field in an enum even when it holds
+ * {@code Set.copyOf(...)}, because it reasons about the declared type. A method has no field to
+ * object to, and it keeps each constant's answer next to the constant.
+ *
+ *
Bean names rather than types, because that is what the guard can ask a {@code
+ * ListableBeanFactory} without instantiating anything: the point is to check the shape of the
+ * graph, not to build it.
+ */
public enum AuthenticationMode {
- JWT,
- REDIS_SESSION
+
+ /** Stateless bearer tokens: a decoder, and no session infrastructure at all. */
+ JWT {
+ @Override
+ public Set requiredBeans() {
+ return Set.of("jwtDecoder");
+ }
+
+ @Override
+ public Set forbiddenBeans() {
+ return Set.of("redisVersionedSessionRepository", "springSessionRepositoryFilter");
+ }
+ },
+
+ /** Server-side sessions in Redis: the repository and its filter, and no decoder. */
+ REDIS_SESSION {
+ @Override
+ public Set requiredBeans() {
+ return Set.of("redisVersionedSessionRepository", "springSessionRepositoryFilter");
+ }
+
+ @Override
+ public Set forbiddenBeans() {
+ return Set.of("jwtDecoder");
+ }
+ };
+
+ /** Beans that must exist for this mode to be able to authenticate a request. */
+ public abstract Set requiredBeans();
+
+ /** Beans that must not exist, because they would authenticate a request the other way. */
+ public abstract Set forbiddenBeans();
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java
index f334f46e..3af317a2 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java
@@ -1,8 +1,7 @@
package dev.caskeleton.bootstrap.settings;
+import dev.caskeleton.bootstrap.runtime.RuntimeWarnings;
import java.time.ZoneId;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -15,17 +14,17 @@ public record LoggingSettings(File file, Async async, Json json) {
public record File(
boolean enabled, String path, String maxSize, int maxHistory, String totalSizeCap) {
- private static final Logger log = LoggerFactory.getLogger(File.class);
public File {
if (enabled) {
if (path == null || path.isBlank()) {
- log.warn(
- "APP_LOG_FILE_PATH is blank while APP_LOG_FILE_ENABLED=true; using 'logs/app.json'");
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_FILE_PATH", path, "logs/app.json", "it is blank while file logging is on");
path = "logs/app.json";
}
if (maxHistory <= 0) {
- log.warn("APP_LOG_FILE_MAX_HISTORY must be >= 1 (got {}); using 14", maxHistory);
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_FILE_MAX_HISTORY", maxHistory, 14, "it must be >= 1");
maxHistory = 14;
}
// Size strings (maxSize, totalSizeCap) are parsed by logback; let it own that contract.
@@ -34,18 +33,17 @@ public record LoggingSettings(File file, Async async, Json json) {
}
public record Async(boolean enabled, int queueSize, int discardingThreshold) {
- private static final Logger log = LoggerFactory.getLogger(Async.class);
public Async {
if (enabled) {
if (queueSize <= 0) {
- log.warn("APP_LOG_ASYNC_QUEUE_SIZE must be >= 1 (got {}); using 512", queueSize);
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_ASYNC_QUEUE_SIZE", queueSize, 512, "it must be >= 1");
queueSize = 512;
}
if (discardingThreshold < 0) {
- log.warn(
- "APP_LOG_ASYNC_DISCARDING_THRESHOLD must be >= 0 (got {}); using 20",
- discardingThreshold);
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_ASYNC_DISCARDING_THRESHOLD", discardingThreshold, 20, "it must be >= 0");
discardingThreshold = 20;
}
}
@@ -54,32 +52,31 @@ public record LoggingSettings(File file, Async async, Json json) {
public record Json(
String timezone, String timestampPattern, boolean includeCallerData, int loggerNameLength) {
- private static final Logger log = LoggerFactory.getLogger(Json.class);
private static final String DEFAULT_TIMEZONE = "UTC";
private static final String DEFAULT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX";
public Json {
if (timezone == null || timezone.isBlank()) {
- log.warn("APP_LOG_JSON_TIMEZONE is blank; using '{}'", DEFAULT_TIMEZONE);
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_JSON_TIMEZONE", timezone, DEFAULT_TIMEZONE, "it is blank");
timezone = DEFAULT_TIMEZONE;
} else if (!"default".equalsIgnoreCase(timezone)) {
try {
ZoneId.of(timezone);
} catch (Exception e) {
- log.warn(
- "APP_LOG_JSON_TIMEZONE '{}' is not a valid IANA zone; using '{}'",
- timezone,
- DEFAULT_TIMEZONE);
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_JSON_TIMEZONE", timezone, DEFAULT_TIMEZONE, "it is not a valid IANA zone");
timezone = DEFAULT_TIMEZONE;
}
}
if (timestampPattern == null || timestampPattern.isBlank()) {
- log.warn("APP_LOG_JSON_TIMESTAMP_PATTERN is blank; using ISO 8601 default");
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_JSON_TIMESTAMP_PATTERN", timestampPattern, DEFAULT_PATTERN, "it is blank");
timestampPattern = DEFAULT_PATTERN;
}
if (loggerNameLength < 0) {
- log.warn(
- "APP_LOG_JSON_LOGGER_NAME_LENGTH must be >= 0 (got {}); using 0", loggerNameLength);
+ RuntimeWarnings.settingFallback(
+ "APP_LOG_JSON_LOGGER_NAME_LENGTH", loggerNameLength, 0, "it must be >= 0");
loggerNameLength = 0;
}
}
diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java
index d237d4c0..4e4de4d8 100644
--- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java
+++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java
@@ -1,8 +1,7 @@
package dev.caskeleton.bootstrap.settings;
+import dev.caskeleton.bootstrap.runtime.RuntimeWarnings;
import java.nio.charset.StandardCharsets;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
@@ -14,16 +13,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "ca-skeleton.privacy")
public record PrivacySettings(String pseudonymizationSalt) {
- private static final Logger log = LoggerFactory.getLogger(PrivacySettings.class);
-
/** Dev/local fallback salt — the {@code __LOCAL_DEV_} prefix is the prod-unsafe sentinel. */
static final String DEV_SENTINEL_SALT = "__LOCAL_DEV_pseudonymization_salt";
public PrivacySettings {
if (pseudonymizationSalt == null || pseudonymizationSalt.isBlank()) {
- log.warn(
- "APP_PRIVACY_PSEUDONYMIZATION_SALT is blank; using a dev sentinel salt. "
- + "Set a real secret-manager value before production.");
+ RuntimeWarnings.settingFallback(
+ "APP_PRIVACY_PSEUDONYMIZATION_SALT",
+ pseudonymizationSalt,
+ "a dev sentinel salt",
+ "it is blank; set a real secret-manager value before production");
pseudonymizationSalt = DEV_SENTINEL_SALT;
}
}
diff --git a/src/app-bootstrap/src/main/resources/META-INF/spring.factories b/src/app-bootstrap/src/main/resources/META-INF/spring.factories
index 4ddb4387..40f0c7bf 100644
--- a/src/app-bootstrap/src/main/resources/META-INF/spring.factories
+++ b/src/app-bootstrap/src/main/resources/META-INF/spring.factories
@@ -2,15 +2,15 @@ org.springframework.boot.EnvironmentPostProcessor=\
dev.caskeleton.bootstrap.activation.MasterSwitchEnvironmentPostProcessor,\
dev.caskeleton.bootstrap.activation.RuntimeEnvironmentProfileValidator,\
dev.caskeleton.bootstrap.activation.CapabilityDependencyEnvironmentValidator,\
-dev.caskeleton.bootstrap.tracing.TracingSamplingEnvironmentPostProcessor,\
-dev.caskeleton.bootstrap.runtime.RedisReadinessGroupPostProcessor,\
-dev.caskeleton.bootstrap.runtime.DatabaseReadinessGroupPostProcessor
+dev.caskeleton.bootstrap.autoconfigure.tracing.TracingSamplingEnvironmentPostProcessor,\
+dev.caskeleton.bootstrap.autoconfigure.redis.RedisReadinessGroupPostProcessor,\
+dev.caskeleton.bootstrap.autoconfigure.jpa.DatabaseReadinessGroupPostProcessor
org.springframework.boot.SpringBootExceptionReporter=\
dev.caskeleton.bootstrap.runtime.startup.StartupFailureExceptionReporter
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
-dev.caskeleton.bootstrap.autoconfigure.persistencejpa.JpaOffAutoConfigurationImportFilter
+dev.caskeleton.bootstrap.autoconfigure.jpa.JpaOffAutoConfigurationImportFilter
# Corrects the structured log's profile field once the environment has resolved. An
# ApplicationListener, not a bean: this event fires before there is a context to hold one.
diff --git a/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 9b104ba1..643c527d 100644
--- a/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1,6 +1,6 @@
dev.caskeleton.bootstrap.autoconfigure.fileserver.FileserverPlatformAutoConfiguration
dev.caskeleton.bootstrap.autoconfigure.httpclient.HttpClientPlatformAutoConfiguration
-dev.caskeleton.bootstrap.autoconfigure.persistencejpa.PersistenceJpaRootAutoConfiguration
+dev.caskeleton.bootstrap.autoconfigure.jpa.PersistenceJpaRootAutoConfiguration
dev.caskeleton.bootstrap.autoconfigure.messaging.DisabledMessagingSentinelAutoConfiguration
-dev.caskeleton.bootstrap.notification.NotificationRootAutoConfiguration
+dev.caskeleton.bootstrap.autoconfigure.notification.NotificationRootAutoConfiguration
dev.caskeleton.bootstrap.activation.AdapterActivationAutoConfiguration
diff --git a/src/app-bootstrap/src/main/resources/application.yml b/src/app-bootstrap/src/main/resources/application.yml
index f9994cc6..37d38464 100644
--- a/src/app-bootstrap/src/main/resources/application.yml
+++ b/src/app-bootstrap/src/main/resources/application.yml
@@ -3,6 +3,13 @@
# input validation lives in the *Settings records under each module.
# =============================================================================
+# 이 파일은 프레임워크 설정(spring.*)과 취합 목록만 가진다.
+#
+# 974줄 한 파일이던 것을 능력별로 나눴다(BOOT-008). spring.* 만 남은 이유는 하나다:
+# spring.profiles.active 는 import 된 문서에 두면 프로파일이 결정되는 시점과 문서가 읽히는 시점이
+# 엇갈린다. 프레임워크 설정은 능력이 아니므로 능력별 파일로 나눌 대상도 아니다.
+#
+# 우선순위: 환경변수/CLI > application-{profile}.yml > application.yml > config/*.yml
spring:
application:
# free-form string (also exposed as the JSON log "app" field)
@@ -222,753 +229,20 @@ spring:
# duration: 30s | 1m | 500ms
timeout-per-shutdown-phase: ${APP_SERVER_SHUTDOWN_TIMEOUT:30s}
-# ---------------------------------------------------------------------------
-# Actuator / management — MERGED ownership at integration:
-# - feature-management-actuator-security-contract owns server.port, the exposure
-# allowlist/exclude, health show-details, shutdown-disabled, and info.
-# - feature-runtime-health-lifecycle-contract owns the health probe SHAPE
-# (probes.enabled + the liveness/readiness/startup groups + dependency taxonomy).
-# ---------------------------------------------------------------------------
-# ---------------------------------------------------------------------------
-# OpenAPI. Stated rather than defaulted: SpringDoc logs a startup WARN telling every deployment to
-# decide this, and a warning that appears on every start is one nobody reads. The template decides —
-# on where it is useful, off under prod, which application-prod.yml pins so the choice cannot be
-# widened by an environment variable in the one runtime where publishing the whole API shape is a
-# reconnaissance surface.
-# ---------------------------------------------------------------------------
-springdoc:
- api-docs:
- enabled: ${APP_OPENAPI_DOCS_ENABLED:true}
- swagger-ui:
- enabled: ${APP_OPENAPI_UI_ENABLED:true}
-
-management:
- # feature-management-actuator-security-contract D1: separate management port so actuator
- # endpoints are not exposed on the same socket as the app API. Default: 9001.
- server:
- port: ${MANAGEMENT_SERVER_PORT:9001}
- endpoints:
- web:
- # D2: production allowlist — only safe, scrape-friendly endpoints are exposed.
- exposure:
- # adapteractivation reports which master switches resolved on and what that makes required.
- # It reads the environment and creates nothing, so it leaks no secret and opens no resource —
- # and it is exposed precisely so a smoke lane can check the application's own answer rather
- # than the flags the lane itself passed in, which is a test of the test.
- include: health,prometheus,info,loggers,adapteractivation
- # D2/D4/D5: explicitly excluded dangerous endpoints (env leaks secrets,
- # heapdump/threaddump = memory forensics, shutdown = remote kill, configprops = secret leak).
- exclude: env,configprops,heapdump,threaddump,shutdown
- endpoint:
- health:
- # D8: never expose health details to unauthenticated callers.
- show-details: when-authorized
- # Membership is validated. With validation off, a group naming a contributor that does not
- # exist is silently dropped and readiness reports UP while proving nothing about the
- # dependency it claims to gate on — a false green that survives exactly as long as nobody
- # checks. On, the cost is that every name below must resolve, in every deployment.
- #
- # That cost is paid by construction rather than by convention: the groups below name only
- # unconditional contributors, and the one conditional member — `redisRequired` — is appended
- # by RedisReadinessGroupPostProcessor from the same predicate that creates the bean. A
- # contributor can therefore be named only where it exists, and a misspelled one still fails
- # startup.
- validate-group-membership: true
- # feature-runtime-health-lifecycle-contract: expose the Kubernetes-ready
- # liveness/readiness/startup probe paths.
- probes:
- enabled: true
- group:
- # Liveness: JVM can continue (OOM → liveness DOWN → pod restart).
- # Only livenessState is included; dependency health MUST NOT be here —
- # a DOWN DB must never trigger a pod restart when the JVM is healthy.
- liveness:
- include: livenessState
- # Readiness: ready to serve traffic AND all REQUIRED dependencies up.
- # Every name here must resolve to a contributor that exists (validate-group-membership
- # above). A correctness-role Redis contributor belongs in this list and is added when the
- # Redis runtime composition creates one; naming it before then produced a readiness probe
- # that reported UP without ever checking Redis.
- # `redisRequired` is NOT listed here, and must not be: group membership does not tolerate a
- # conditional contributor being absent. Boot validates the group against the contributors
- # that exist, so naming it statically made every Redis-off and cache-only deployment fail at
- # startup with "Included health contributor 'redisRequired' in group 'readiness' does not
- # exist". RedisReadinessGroupPostProcessor appends it to this list — from the same predicate
- # that creates the bean — when Redis is on and a correctness role selected it.
- # `redisOptional` is never appended: a cache outage is reported as degraded detail and never
- # turns a healthy JVM or an otherwise-ready pod unavailable.
- readiness:
- include: readinessState
- # Startup: startup/migration validation complete.
- # readinessState acts as the startup completion gate — it flips UP only
- # after the context is fully initialized (Flyway migration included).
- startup:
- include: readinessState
- shutdown:
- # D4: shutdown endpoint disabled globally — even if somehow exposed, it cannot be invoked.
- access: none
- info:
- build:
- # Build-info only (no env leak).
- enabled: true
-
-server:
- # integer 1-65535
- port: ${APP_SERVER_PORT:8080}
- # graceful | immediate
- shutdown: ${APP_SERVER_SHUTDOWN:graceful}
- # none | native | framework (trust X-Forwarded-* behind LB/proxy)
- forward-headers-strategy: ${APP_SERVER_FORWARD_HEADERS_STRATEGY:framework}
- tomcat:
- threads:
- # integer >= 1
- max: ${APP_SERVER_TOMCAT_MAX_THREADS:200}
- # integer >= 0
- min-spare: ${APP_SERVER_TOMCAT_MIN_SPARE_THREADS:10}
- # integer >= 0 (OS-level backlog queue depth)
- accept-count: ${APP_SERVER_TOMCAT_ACCEPT_COUNT:100}
- # integer >= 1 (hard cap on simultaneously open connections)
- max-connections: ${APP_SERVER_TOMCAT_MAX_CONNECTIONS:8192}
- # duration: 20s | 1m
- connection-timeout: ${APP_SERVER_TOMCAT_CONNECTION_TIMEOUT:20s}
- compression:
- # true | false
- enabled: ${APP_SERVER_COMPRESSION_ENABLED:true}
- # bytes or sized: 1024 | 1KB | 2KB
- min-response-size: ${APP_SERVER_COMPRESSION_MIN_RESPONSE_SIZE:1KB}
-logging:
- level:
- # TRACE | DEBUG | INFO | WARN | ERROR | OFF
- #
- # Inline defaults, because these had none and only application-local.yml pinned them. That made
- # local the one environment that could start without an operator supplying every level: dev and
- # prod failed to bind `logging.level.root` before reaching any of their own settings, which is a
- # log-configuration problem wearing a startup failure's clothes.
- root: ${APP_LOG_LEVEL_ROOT:INFO}
- dev.caskeleton: ${APP_LOG_LEVEL_APP:INFO}
- org.springframework: ${APP_LOG_LEVEL_SPRING:INFO}
- org.springframework.web: ${APP_LOG_LEVEL_WEB:INFO}
- # DEBUG here prints SQL once JPA/jdbc is wired in
- org.hibernate.SQL: ${APP_LOG_LEVEL_SQL:INFO}
-
-# Module-scoped knobs. Each block is bound into a *Settings @ConfigurationProperties
-# record in the corresponding module, which is where allowed-value validation lives.
-ca-skeleton:
- # The five optional adapters ship in this artifact and are off unless a deployment says otherwise,
- # so one jar serves every environment and an operator turns a capability on with one variable. Off
- # is structural: no settings bind, no bean is created, no socket, pool or thread exists, and a
- # malformed detail setting for a capability nobody enabled does not block startup.
- # Registry SSOT: docs/registries/env-keys.yaml.
- persistence-jpa:
- # true | false — relational persistence: DataSource, entity scan, repositories, Flyway.
- enabled: ${APP_PERSISTENCE_JPA_ENABLED:false}
- persistence-mongo:
- # true | false
- enabled: ${APP_PERSISTENCE_MONGO_ENABLED:false}
- # Required non-blank when the switch above is true. Exactly one profile: the runtime builds one
- # sync client and one pool, and never resolves a non-selected profile's secret.
- active-profile: ${APP_PERSISTENCE_MONGO_ACTIVE_PROFILE:}
- # Canonical HTTP capability activation. Bindings are the sole activation SSOT: provider
- # definitions alone are inert, and the current NOT_IMPLEMENTED readiness card rejects ACTIVE
- # before any client/executor/pool resource can be created.
- capabilities:
- # Every capability below renders its keys under one namespace — app.redis.namespace, which is
- # {environment}:{service}:{domain}. The per-capability namespace-application /
- # namespace-environment pairs that used to live here are gone: four capabilities each joining
- # two free-form tokens in their own order produced four different key prefixes, and the ACL
- # pattern meant to fence the deployment in matched none of them.
- cache:
- # Canonical semantic cache activation. Disabled by default; "redis" composes
- # RedisCacheRegionAdapter for the region named below and resolves HMAC material by reference.
- bindings:
- default: ${APP_CACHE_CANONICAL_DEFAULT_PROVIDER:disabled}
- semantic-region: ${APP_CACHE_REDIS_SEMANTIC_REGION:default}
- key-version: 1
- key-hmac-secret-reference: secret://environment/APP_CACHE_REDIS_KEY_HMAC_SECRET
- command-timeout: ${APP_CACHE_REDIS_COMMAND_TIMEOUT:200ms}
- positive-soft-ttl: ${APP_CACHE_REDIS_POSITIVE_SOFT_TTL:30s}
- positive-hard-ttl: ${APP_CACHE_REDIS_POSITIVE_HARD_TTL:5m}
- negative-ttl: ${APP_CACHE_REDIS_NEGATIVE_TTL:10s}
- # Floor on the hard TTL. Below it, entries expire faster than the round trip that wrote them:
- # every read misses and every miss writes.
- minimum-hard-ttl: ${APP_CACHE_REDIS_MINIMUM_HARD_TTL:1s}
- idempotency:
- # disabled | jdbc | redis. JDBC is the existing V1 provider; Redis is the owner-safe V2
- # provider. They are mutually exclusive and no V1-to-V2 facade is inferred.
- provider: ${APP_IDEMPOTENCY_PROVIDER:disabled}
- key-hmac-secret-reference: secret://environment/APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET
- key-version: 1
- command-timeout: ${APP_IDEMPOTENCY_REDIS_COMMAND_TIMEOUT:200ms}
- processing-lease: ${APP_IDEMPOTENCY_PROCESSING_LEASE:30s}
- replay-ttl: ${APP_IDEMPOTENCY_TTL:24h}
- failure-retention: ${APP_IDEMPOTENCY_FAILURE_RETENTION:24h}
- response-codec-id: json-v2
- # Numeric: the owner-safe claim writes this into the record and compares it there.
- policy-revision: 2
- lease:
- # disabled | redis. This is EFFICIENCY_ONLY and never supplies fencing.
- provider: ${APP_LEASE_PROVIDER:disabled}
- key-hmac-secret-reference: secret://environment/APP_LEASE_REDIS_KEY_HMAC_SECRET
- key-version: 1
- command-timeout: ${APP_LEASE_REDIS_COMMAND_TIMEOUT:200ms}
- contention-retry-after: ${APP_LEASE_REDIS_CONTENTION_RETRY_AFTER:50ms}
- # How much shorter than the server's TTL this holder considers its lease valid. The two
- # clocks are not the same clock, and a holder that measured the full TTL locally would still
- # believe it held a lease the server had already handed to somebody else.
- drift-budget: ${APP_LEASE_REDIS_DRIFT_BUDGET:10ms}
- rate-limit:
- # disabled | redis. This is the sole outbound provider activation selector.
- provider: ${APP_RATE_LIMIT_PROVIDER:disabled}
- failure-policy: ${APP_RATE_LIMIT_FAILURE_POLICY:fail-closed}
- default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default}
- failure-retry-after: ${APP_RATE_LIMIT_FAILURE_RETRY_AFTER:100ms}
- key-version: ${APP_RATE_LIMIT_KEY_VERSION:1}
- command-timeout: ${APP_RATE_LIMIT_REDIS_COMMAND_TIMEOUT:200ms}
- policies:
- api-default:
- revision: ${APP_RATE_LIMIT_POLICY_REVISION:v1}
- algorithm: ${APP_RATE_LIMIT_ALGORITHM:sliding-counter}
- limit: ${APP_RATE_LIMIT_LIMIT:100}
- window: ${APP_RATE_LIMIT_WINDOW:1s}
- capacity: ${APP_RATE_LIMIT_CAPACITY:100}
- refill-tokens: ${APP_RATE_LIMIT_REFILL_TOKENS:100}
- refill-period: ${APP_RATE_LIMIT_REFILL_PERIOD:1s}
- maximum-cost: ${APP_RATE_LIMIT_MAXIMUM_COST:10}
- cleanup-grace: ${APP_RATE_LIMIT_CLEANUP_GRACE:5s}
- maximum-clock-regression: ${APP_RATE_LIMIT_MAXIMUM_CLOCK_REGRESSION:250ms}
- security:
- bootstrap:
- # required, non-blank — startup fails if blank (see BootstrapSettings)
- app-name: ${APP_NAME}
- runtime:
- # feature-env-driven-runtime-configuration D8: enforced at startup by
- # StartupSafetyValidator. The two prod-unsafe toggles fail startup if true under
- # the prod profile; multi-instance fails startup if its coordination beans are absent.
- # true | false (internal error detail in responses — forbidden under prod)
- error-detail-exposure-enabled: ${APP_ERROR_DETAIL_EXPOSURE_ENABLED:false}
- # true | false (request/response body capture in logs — forbidden under prod)
- log-body-capture-enabled: ${APP_LOG_BODY_CAPTURE_ENABLED:false}
- # true | false (requires the 5 instance-coordination beans when true)
- multi-instance-enabled: ${APP_MULTI_INSTANCE_ENABLED:false}
- # true | false (whether to run Flyway migrations automatically at startup)
- migration-on-startup: ${APP_MIGRATION_ON_STARTUP:true}
- # feature-distributed-lock-contract D5 — distributed lock acquisition contract (try-lock +
- # finite wait + lease TTL). Bound to LockSettings (adapter-persistence). Plain values, NOT
- # APP_* env keys — new env-key registration is feature-env-driven-runtime-configuration's
- # domain (out of scope for this branch); code defaults in LockSettings mirror these.
- lock:
- wait-time: 3s
- lease-ttl: 30s
- # Which RDBMS vendor composition the persistence adapter registers: postgresql | h2. Bound to
- # PersistenceVendorSettings (adapter-persistence), which rejects any other value at startup.
- #
- # A property rather than a profile check, because the vendor belongs to the datastore and not to
- # the environment that happens to use it. The profiles then state their choice:
- # application-local.yml selects h2, application-dev.yml and application-prod.yml select
- # postgresql, and prod additionally refuses h2 through PersistenceVendorProdSafetyValidator.
- #
- # The value here is the default for a deployment that activates no profile at all — PostgreSQL,
- # the vendor every deployment ran before this selector existed.
- # Notification delivery platform. Disabled by default, and disabled means nothing is bound: no
- # provider runtime, no schema activation check, no scheduler thread, no secret required. Enabling
- # it needs a real database with the notification schema stream applied and at least one provider
- # profile with credentials — see docs/notification/configuration-reference.md, which is the
- # reference this tree is kept in step with.
- #
- # Every value carries an inline default so that a deployment leaving the platform off never has to
- # supply an environment variable for a subsystem it is not running. The tree existed only as
- # relaxed-binding environment variables until 2026-08-15: a template user could not discover which
- # profile, secret, callback and activation settings had to line up, because nothing wrote them
- # down together.
- notification:
- platform:
- # The master switch. False binds nothing at all.
- enabled: ${APP_NOTIFICATION_PLATFORM_ENABLED:false}
- # SERVING | INGEST_ONLY. SERVING refuses to start without a working provider; INGEST_ONLY
- # stores requests and does not dispatch, which is the shape a migration or a drain wants.
- mode: ${APP_NOTIFICATION_PLATFORM_MODE:SERVING}
- dispatch:
- claim-batch-size: ${APP_NOTIFICATION_PLATFORM_CLAIM_BATCH_SIZE:50}
- # The lease must outlast a provider call plus its timeout, or a live worker's delivery is
- # claimed by a second one while the first is still waiting on the provider.
- lease-duration: ${APP_NOTIFICATION_PLATFORM_LEASE_DURATION:2m}
- poll-interval: ${APP_NOTIFICATION_PLATFORM_POLL_INTERVAL:1s}
- max-global-concurrency: ${APP_NOTIFICATION_PLATFORM_MAX_CONCURRENCY:64}
- max-additional-attempts: ${APP_NOTIFICATION_PLATFORM_MAX_ADDITIONAL_ATTEMPTS:4}
- max-queue-age: ${APP_NOTIFICATION_PLATFORM_MAX_QUEUE_AGE:24h}
- # An ambiguous attempt means the request reached the provider and the outcome could not be
- # read. Falling back would risk sending the same notification twice, so it is off.
- allow-ambiguous-fallback: ${APP_NOTIFICATION_PLATFORM_ALLOW_AMBIGUOUS_FALLBACK:false}
- callbacks:
- enabled: ${APP_NOTIFICATION_PLATFORM_CALLBACKS_ENABLED:false}
- # Ceiling is 65508 = the ciphertext column minus the AES-GCM nonce and tag. A larger value
- # is refused at binding rather than at the database, after the callback was acknowledged.
- max-body-bytes: ${APP_NOTIFICATION_PLATFORM_CALLBACK_MAX_BODY_BYTES:65508}
- replay-skew: ${APP_NOTIFICATION_PLATFORM_CALLBACK_REPLAY_SKEW:5m}
- # Peers whose Forwarded/X-Forwarded-* headers may be believed when reconstructing the URL a
- # provider signed. Empty by default, and that is the safe default rather than the convenient
- # one: with no entry the resolver uses what the container observed, so no caller can choose
- # the URL its own signature is checked against. A deployment behind a load balancer lists its
- # proxies here.
- trusted-proxies: ${APP_NOTIFICATION_PLATFORM_CALLBACK_TRUSTED_PROXIES:}
- # Provider profiles, keyed by profile id. None by default: an empty map with mode=SERVING is
- # refused at startup, which is the intended outcome for "enabled but not configured".
- #
- # A profile looks like this — copy it under a profile id of your own and supply the secrets
- # through the credential profile, never inline:
- #
- # providers:
- # ses-primary:
- # type: SES # APNS | FCM | SES | SMTP | TWILIO | WEB_PUSH | WEBHOOK
- # enabled: true
- # primary-for-channel: true # exactly one primary per channel
- # environment: PRODUCTION
- # credential-profile: ses-prod # resolved through SecretMaterialProvider
- # timeout: 10s
- # max-concurrency: 16
- # rate-per-second: 50
- # One shipped profile, disabled, because a map cannot be written by an environment variable.
- #
- # `ca-skeleton` contains a hyphen and an environment variable cannot express one — Spring maps
- # `_` to `.`, never to `-` — so every setting in this tree reaches a deployment through an
- # APP_ placeholder named here. A provider map with caller-chosen keys has no placeholder to be
- # named by, which is why the tree shipped as `providers: {}` and no deployment could add one.
- #
- # So the template ships the reference family as one profile, off. A deployment that wants a
- # different id or a second family declares it in its own YAML; a deployment that wants the
- # common case sets APP_NOTIFICATION_PLATFORM_SMTP_ENABLED=true and the relay address below.
- #
- # Disabled profiles are skipped by assembly before their type is resolved, so this costs a
- # deployment that does not want it exactly nothing.
- providers:
- smtp:
- type: SMTP
- enabled: ${APP_NOTIFICATION_PLATFORM_SMTP_ENABLED:false}
- # Exactly one primary per channel; a second primary for EMAIL fails assembly by name.
- primary-for-channel: ${APP_NOTIFICATION_PLATFORM_SMTP_PRIMARY:true}
- environment: ${APP_NOTIFICATION_PLATFORM_SMTP_ENVIRONMENT:local}
- credential-profile: ${APP_NOTIFICATION_PLATFORM_SMTP_CREDENTIAL_PROFILE:default}
- timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_TIMEOUT:10s}
- max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_MAX_CONCURRENCY:4}
- rate-per-second: ${APP_NOTIFICATION_PLATFORM_SMTP_RATE_PER_SECOND:10}
- # What the relay is, as opposed to which profile uses it. Host, port and credentials stay
- # spring.mail.*; these are the parts Spring's mail properties have no word for.
- smtp:
- # STARTTLS_REQUIRED | IMPLICIT_TLS. There is no plaintext member: the type refuses an
- # unencrypted relay by construction rather than by a validator somebody has to run.
- tls-mode: ${APP_NOTIFICATION_PLATFORM_SMTP_TLS_MODE:STARTTLS_REQUIRED}
- # example.invalid is reserved by RFC 2606 and resolves nowhere, so a deployment that forgot
- # to set one gets a traceable bounce rather than mail apparently from an address it does not
- # own.
- sender-identity: ${APP_NOTIFICATION_PLATFORM_SMTP_SENDER_IDENTITY:no-reply@example.invalid}
- connect-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_CONNECT_TIMEOUT:5s}
- read-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_READ_TIMEOUT:10s}
- write-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_WRITE_TIMEOUT:10s}
- max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_DISPATCH_CONCURRENCY:4}
- # ---- Key material -------------------------------------------------------
- # Eight purposes, eight keys, each base64 of at least 32 bytes and each distinct from every
- # other. The platform decodes them at startup and refuses to boot if one is blank, short or
- # shared: a blank value used to be skipped, so the platform started without the key and found
- # out on the first contact point — in production, on a user's notification.
- #
- # These were bound by nothing until now. The subtree was missing from this file entirely while
- # the settings record and its validator existed, so `enabled: true` was an unconditional boot
- # failure in every environment and the only way to discover the eight names was to read the
- # record. That is the gap this block closes; the empty defaults keep a deployment with the
- # platform off from having to supply anything.
- #
- # Never write a value here. Supply them out of band, per environment.
- secrets:
- contact-encryption-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY:}
- contact-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY:}
- callback-signing-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY:}
- provider-credential-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY:}
- payload-encryption-key: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY:}
- vapid-signing-key: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY:}
- provider-request-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY:}
- callback-fingerprint-hmac-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY:}
- # The id written into every envelope this key produces, one per purpose, keyed by the
- # purpose's own name. Not secret — an id identifies key material without revealing it — but
- # required all the same, and with no default on purpose: a constant id makes a rotation
- # indistinguishable from the key it replaced, so nothing could decrypt what was written
- # before it. Change the id in the same deployment that changes the material.
- active-key-ids:
- CONTACT_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY_ID:}
- CONTACT_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY_ID:}
- CALLBACK_SIGNING: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY_ID:}
- PROVIDER_CREDENTIAL: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY_ID:}
- PAYLOAD_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY_ID:}
- VAPID_SIGNING: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY_ID:}
- PROVIDER_REQUEST_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY_ID:}
- CALLBACK_FINGERPRINT_HMAC: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY_ID:}
- # Superseded material, kept readable, grouped by the purpose it served and then by the id
- # it was active under — historical-keys.PAYLOAD_ENCRYPTION.payload-2026-01. The purpose is
- # not recoverable from an id, and every consumer that resolves a key by id refuses one
- # carrying the wrong purpose, so a retired key filed under the wrong heading resolves and
- # is then rejected. Unset until a first rotation; a deployment that has never rotated needs
- # none. See docs/notification/at-rest-threat-model.md for the rotation sequence.
- historical-keys: {}
-
- persistence:
- vendor: postgresql
- # JPA named-policy deadline envelope. JpaTransactionSettings validates the hierarchy;
- # SpringPolicyTransactionPort intersects these limits with the caller's absolute CallBudget
- # and the actual Hikari connection timeout before acquiring a transaction.
- jpa:
- transaction:
- transaction-timeout: 30s
- begin-budget: 250ms
- minimum-action-window: 1s
- completion-margin: 500ms
- statement-timeout: 10s
- lock-timeout: 2s
- idle-guard-timeout: 15s
- transaction-margin: 250ms
- lock-margin: 100ms
- retry-base-delay: 10ms
- retry-maximum-delay: 50ms
- retry-maximum-attempts: 2
- presentation:
- # feature-api-contract-baseline D2: API version prefix. Default is the URI
- # prefix "/v1" (major-version path, AIP-185); override via env, or set "" for
- # no prefix. The supplemental "X-Api-Version" header never overrides the path.
- api-base-path: ${PRESENTATION_API_BASE_PATH:/v1}
- idempotency:
- # feature-rate-limit-idempotency-contract D6/§E. ttl is env-driven (<=72h,
- # validated in IdempotencyProperties); reaper-interval is literal operational tuning.
- ttl: ${APP_IDEMPOTENCY_TTL:24h}
- reaper-interval: 10m
- async:
- # feature-background-job-async-contract D7 — @Async ThreadPoolTaskExecutor pool sizing.
- # Registry SSOT: docs/registries/env-keys.yaml (APP_ASYNC_EXECUTOR_* rows 1337-1377).
- # Bound into AsyncExecutorSettings @ConfigurationProperties(prefix = "ca-skeleton.async.executor").
- # Bounded queue is mandatory (D7) — an unbounded queue makes max-size unreachable.
- executor:
- # int >= 1 (positive_int). Always-alive worker count.
- core-size: ${APP_ASYNC_EXECUTOR_CORE_SIZE:10}
- # int >= core-size (positive_int_ge_core). Hard ceiling on workers.
- max-size: ${APP_ASYNC_EXECUTOR_MAX_SIZE:50}
- # int in 1..= 1 — maximum rows claimed per relay cycle
- batch-size: 20
- # ISO-8601 duration — IN_FLIGHT orphan visibility window (I6: next_attempt_at reuse)
- in-flight-timeout: PT5M
- # ISO-8601 duration — read also by adapter-persistence OutboxReaper @Scheduled
- reaper-interval: PT10M
- # ISO-8601 duration — PUBLISHED row retention before reaper deletes them (I3)
- # read also by adapter-persistence OutboxReaper via ${ca-skeleton.outbox.published-retention:P7D}
- published-retention: P7D
- security:
- # jwt | redis-session; the bootstrap composition validator rejects mixed infrastructure.
- auth-mode: ${APP_SECURITY_AUTH_MODE:jwt}
- # Required only in jwt mode.
- issuer-uri: ${APP_SECURITY_JWT_ISSUER}
- # blank to skip audience check
- audience: ${APP_SECURITY_JWT_AUDIENCE}
- # comma-separated list (Spring binds to List)
- #
- # Derived from the API base path, not restated. The default here was the literal
- # /api/healthcheck while presentation.api-base-path defaults to /v1, so the two shipped defaults
- # described one address and disagreed about it: the health endpoint was published at
- # /v1/healthcheck and the allowlist opened /api/healthcheck, a path no handler serves. The
- # public health endpoint was therefore not public in any runtime except local, which pins both
- # to /api and so never saw it. Found by the shared-infra-dev lane, where the smoke client got a
- # refusal from an application that had started perfectly.
- public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck}
- session:
- cookie-name: ${APP_SESSION_COOKIE_NAME:CA_SESSION}
- secure: ${APP_SESSION_COOKIE_SECURE:true}
- http-only: ${APP_SESSION_COOKIE_HTTP_ONLY:true}
- same-site: ${APP_SESSION_COOKIE_SAME_SITE:Lax}
- path: ${APP_SESSION_COOKIE_PATH:/}
- csrf-cookie-name: ${APP_SESSION_CSRF_COOKIE_NAME:XSRF-TOKEN}
- csrf-header-name: ${APP_SESSION_CSRF_HEADER_NAME:X-XSRF-TOKEN}
- authz:
- # feature-authentication-authorization-contract D2/D3/D8: app-side role→permission
- # mapping (the default source; IdP-issued permission claims are an out-of-scope
- # alternative). Keys are RAW IdP role names (no ROLE_ prefix — that prefix only
- # exists on Spring authorities, not on the principal's raw role set), looked up
- # case-insensitively. Values are explicitly enumerated `resource:action` permissions
- # (no wildcards — least-privilege, OWASP-AUTHZ-C4). The values below are the
- # sample-portfolio demonstration (§5): `user` may read/write, only `admin` may close.
- role-permissions:
- user: worklog:read,worklog:write
- admin: worklog:read,worklog:write,worklog:close
- # The notification platform's own two permissions, granted to two roles rather than one.
- # Publishing a template changes what every future submission renders, so a service that may
- # send notifications is not thereby a service that may rewrite them; a deployment that wants
- # both grants both roles.
- notification-submit: notification:submit
- notification-template-publish: notification-template:publish
- cors:
- # true | false
- enabled: ${APP_SECURITY_CORS_ENABLED:false}
- # comma-separated. Empty by default, unlike the other undefaulted values: CORS is off by
- # default, and an empty origin list is the safest possible value rather than an arbitrary one.
- # The rule those others follow — no default for an address, a credential, or an identity — is
- # about not running against something nobody chose; allowing nobody is a choice.
- allowed-origins: ${APP_SECURITY_CORS_ORIGINS:}
- # comma-separated; empty -> defaults
- allowed-methods: ${APP_SECURITY_CORS_ALLOWED_METHODS:}
- # comma-separated; "*" allows any
- allowed-headers: ${APP_SECURITY_CORS_ALLOWED_HEADERS:*}
- # true | false
- allow-credentials: ${APP_SECURITY_CORS_ALLOW_CREDENTIALS:false}
- # seconds
- max-age-seconds: ${APP_SECURITY_CORS_MAX_AGE:600}
- logging:
- file:
- # true | false (wraps console + adds rolling JSON file appender)
- enabled: ${APP_LOG_FILE_ENABLED:false}
- # relative (to src/) or absolute
- path: ${APP_LOG_FILE_PATH:logs/ca-skeleton.json}
- # size with unit: KB | MB | GB
- max-size: ${APP_LOG_FILE_MAX_SIZE:100MB}
- # integer >= 1
- max-history: ${APP_LOG_FILE_MAX_HISTORY:14}
- # size with unit or 0
- total-size-cap: ${APP_LOG_FILE_TOTAL_SIZE_CAP:3GB}
- async:
- # true | false (AsyncAppender wrapper for non-blocking I/O)
- enabled: ${APP_LOG_ASYNC_ENABLED:true}
- # integer >= 1
- queue-size: ${APP_LOG_ASYNC_QUEUE_SIZE:512}
- # integer >= 0 (0 = never drop)
- discarding-threshold: ${APP_LOG_ASYNC_DISCARDING_THRESHOLD:20}
- json:
- # IANA timezone (UTC | Asia/Seoul | ...) or "default"
- timezone: ${APP_LOG_JSON_TIMEZONE:UTC}
- # ISO 8601 pattern
- timestamp-pattern: ${APP_LOG_JSON_TIMESTAMP_PATTERN:yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
- # true | false (file/method/line — expensive)
- include-caller-data: ${APP_LOG_JSON_INCLUDE_CALLER_DATA:false}
- # integer; 0 = full name, positive = abbreviated
- logger-name-length: ${APP_LOG_JSON_LOGGER_NAME_LENGTH:0}
- # float in [0.0, 1.0] — keep-probability for <=INFO logs (prod 0.1 = 10% sampling;
- # WARN/ERROR always 100%). Consumed by SamplingTurboFilter via logback .
- sampling-rate: ${APP_LOG_SAMPLING_RATE:1.0}
- privacy:
- # secret-tier HMAC salt for user_principal pseudonymization (DRIFT-6; algorithm SSOT
- # feature-data-retention-privacy-contract). Blank -> PrivacySettings warns + uses a dev
- # sentinel; prod MUST supply a real secret-manager value.
- pseudonymization-salt: ${APP_PRIVACY_PSEUDONYMIZATION_SALT:}
- # feature-distributed-tracing-contract D1/D4/D6 — tracing seam settings.
- # The OTel/Micrometer tracer runtime is active in this repo (Plan A — seam activated):
- # micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp are wired in
- # app-bootstrap/build.gradle. The exporter remains off while OTEL_EXPORTER_OTLP_ENDPOINT
- # is blank (url_or_empty default). A fork can supply the endpoint via env or override
- # SpanErrorRecorder with its own bean.
- # D4 disabled-fallback: even when enabled=false, RequestLoggingFilter always
- # generates a W3C traceparent so meta.traceId and log trace_id are never null.
- # D6 per-profile defaults: prod=0.01 / staging=0.10 / dev·local=1.0.
- # APP_TRACING_SAMPLE_RATE overrides the per-profile default when set.
- tracing:
- # true | false (boolean_strict). Tracing seam on/off.
- enabled: ${APP_TRACING_ENABLED:true}
- # float in [0.0, 1.0]. Per-profile override (D6 float_between_0_and_1 validation at startup).
- # blank = per-profile default via TracingSampleRateResolver (D-1 ISSUE-1 fix).
- sample-rate: ${APP_TRACING_SAMPLE_RATE:}
- exporter:
- # url_or_empty: blank = exporter off (D1 SEAM default); non-blank must be a valid URL.
- otlp-endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:}
-
-# feature-integration-adapter-templates — optional integration adapter toggles.
-# The `app...enabled` flags feed Spring @ConditionalOnProperty (Layer 1):
-# disabled (default): messaging binds a fail-fast Disabled* sentinel bean; cache/notification
-# contribute nothing and fail fast in their router (CacheStoreRouter / RoutingNotifier) on
-# unbound access. Enabled registers the real adapter
-# (which needs its project-supplied integration client bean). Domain namespace, NOT a
-# generic `app.adapter.*` prefix (branch-note §Audit A1). Env keys are the registry SSOT.
-app:
- # The one global Redis switch. False (the default) means no Redis settings are bound, no Redis
- # credential is required, no client, connection, thread or health contributor is created, and no
- # Redis-specific resource is read. There is deliberately no second master switch: a role such as
- # cache or session selects *which* Redis capabilities compose, never *whether* Redis exists.
- redis:
- enabled: ${APP_REDIS_ENABLED:false}
- # Fileserver R2 exact destination/provider composition. Disabled by default: while false,
- # these blank attestation placeholders do not create directories, probe a filesystem, or
- # contribute FilePublicationPort. Enabling fails closed unless every local-persistent
- # attestation value matches the pre-provisioned root. No implicit local fallback exists.
- fileserver:
- enabled: ${APP_FILESERVER_ENABLED:false}
- destinations:
- local-export:
- provider-ref: local-primary
- required-publication: unique-atomic-create
- required-durability: file-and-directory-sync
- maximum-rows: 1000000
- maximum-encoded-bytes: 1073741824
- providers:
- local-primary:
- # local-persistent is the only implemented/qualified R2 provider.
- # shared-mounted/NFS and SFTP settings must not be added before their providers exist.
- type: local-persistent
- root-directory: ${APP_FILESERVER_LOCAL_ROOT:}
- auto-create: false
- strict-path-security: true
- expected-file-store-name: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME:}
- expected-file-store-type: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE:}
- mount-sentinel-name: .ca-fileserver-volume
- mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:}
- expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:}
- maximum-root-mode: "0750"
- # HTTP Fileserver platform — a different capability from app.fileserver above, which publishes
- # tabular exports. This one serves upload/download/lifecycle routes over HTTP and owns its own
- # namespace so the two cannot be switched on by accident together.
- #
- # While enabled=false none of the detail below is bound: FileserverPlatformAutoConfiguration
- # binds the block itself, and it is not processed until the master switch is true. Unknown keys
- # under this prefix are refused rather than ignored.
- # Outbound HTTP Client platform. Only the master switch lives here.
- #
- # The per-client surface is an indexed list, and templating one element would materialise a
- # nameless client in every deployment — which the settings' own aggregate validation refuses, so
- # the template could not be left in place. Clients are therefore declared straight from the
- # environment as APP_HTTPCLIENT_CLIENTS_0_*, registered in
- # docs/httpclient/env-fields.yaml, and an APP_HTTPCLIENT_ variable absent from that
- # registry fails startup rather than being ignored.
- #
- # While enabled=false none of it is bound: HttpClientPlatformAutoConfiguration binds the block
- # itself and is not processed until the master switch is true.
- httpclient:
- enabled: ${APP_HTTPCLIENT_ENABLED:false}
- fileserver-platform:
- enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false}
- # Writer-lease owner. Must be unique per instance in a multi-instance deployment; the startup
- # gate treats the default as single-instance.
- instance-id: ${APP_FILESERVER_PLATFORM_INSTANCE_ID:local-node}
- default-namespace: ${APP_FILESERVER_PLATFORM_DEFAULT_NAMESPACE:default}
- storage:
- # Absolute, on its own volume, and never under a web or configuration root. A relative path
- # resolves against the process working directory, which differs between a container and a
- # test, so it is refused.
- root: ${APP_FILESERVER_PLATFORM_STORAGE_ROOT:/var/lib/backend/files}
- publish-mode: ${APP_FILESERVER_PLATFORM_STORAGE_PUBLISH_MODE:atomic-move-preferred}
- buffer-size: ${APP_FILESERVER_PLATFORM_STORAGE_BUFFER_SIZE:128KB}
- forbidden-root-ancestors: ${APP_FILESERVER_PLATFORM_STORAGE_FORBIDDEN_ROOT_ANCESTORS:/app,/etc,/usr/share/nginx/html}
- upload:
- # Shared with spring.servlet.multipart below through the same placeholder. Two independent
- # limits would let the servlet container reject an upload the Fileserver policy allows,
- # before any Fileserver code — including its error mapping — ever runs.
- max-file-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_FILE_SIZE:100MB}
- max-request-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_REQUEST_SIZE:110MB}
- initial-reservation: ${APP_FILESERVER_PLATFORM_UPLOAD_INITIAL_RESERVATION:8MB}
- max-parts: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_PARTS:16}
- ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_TTL:1h}
- reservation-ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_RESERVATION_TTL:24h}
- lease-duration: ${APP_FILESERVER_PLATFORM_UPLOAD_LEASE_DURATION:30s}
- require-content-length: ${APP_FILESERVER_PLATFORM_UPLOAD_REQUIRE_CONTENT_LENGTH:false}
- download:
- cache-control: ${APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL:private, no-store}
- inline-allowed: ${APP_FILESERVER_PLATFORM_DOWNLOAD_INLINE_ALLOWED:false}
- max-ranges: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGES:1}
- # Applies to the single-range profile too, so the ceiling is not inert by default.
- max-range-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGE_BYTES:100MB}
- zero-copy-enabled: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_ENABLED:true}
- zero-copy-minimum-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_MINIMUM_BYTES:16MB}
- transfer:
- core-size: ${APP_FILESERVER_PLATFORM_TRANSFER_CORE_SIZE:8}
- max-size: ${APP_FILESERVER_PLATFORM_TRANSFER_MAX_SIZE:32}
- queue-capacity: ${APP_FILESERVER_PLATFORM_TRANSFER_QUEUE_CAPACITY:64}
- await-seconds: ${APP_FILESERVER_PLATFORM_TRANSFER_AWAIT_SECONDS:300}
- security:
- # required | role-based | unenforced. `required` has no built-in policy and fails startup
- # unless the deployment supplies a FileAccessPolicy bean; `unenforced` is refused under a
- # production profile.
- access-policy: ${APP_FILESERVER_PLATFORM_SECURITY_ACCESS_POLICY:required}
- read-roles: ${APP_FILESERVER_PLATFORM_SECURITY_READ_ROLES:ROLE_FILE_READ}
- write-roles: ${APP_FILESERVER_PLATFORM_SECURITY_WRITE_ROLES:ROLE_FILE_WRITE}
- admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN}
- verification:
- timeout: ${APP_FILESERVER_PLATFORM_VERIFICATION_TIMEOUT:5s}
- require-media-type-verdict: ${APP_FILESERVER_PLATFORM_VERIFICATION_REQUIRE_MEDIA_TYPE_VERDICT:false}
- inline-safe-profile: ${APP_FILESERVER_PLATFORM_VERIFICATION_INLINE_SAFE_PROFILE:false}
- quota:
- instance-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_INSTANCE_UPLOAD_PERMITS:16}
- scope-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_SCOPE_UPLOAD_PERMITS:4}
- direct-download-permits: ${APP_FILESERVER_PLATFORM_QUOTA_DIRECT_DOWNLOAD_PERMITS:64}
- soft-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_SOFT_HIGH_WATER:0.70}
- hard-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_HARD_HIGH_WATER:0.85}
- admin:
- enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false}
- orphan-minimum-age: ${APP_FILESERVER_PLATFORM_ADMIN_ORPHAN_MINIMUM_AGE:1h}
- cleanup:
- enabled: ${APP_FILESERVER_PLATFORM_CLEANUP_ENABLED:false}
- interval: ${APP_FILESERVER_PLATFORM_CLEANUP_INTERVAL:60s}
- max-items: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_ITEMS:100}
- max-bytes: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_BYTES:1GB}
- retry-backoff: ${APP_FILESERVER_PLATFORM_CLEANUP_RETRY_BACKOFF:5m}
- tus:
- enabled: ${APP_FILESERVER_PLATFORM_TUS_ENABLED:false}
- httpbis-draft12:
- # Unratified protocol; the contract can change without notice.
- enabled: ${APP_FILESERVER_PLATFORM_HTTPBIS_DRAFT12_ENABLED:false}
- nginx:
- enabled: ${APP_FILESERVER_PLATFORM_NGINX_ENABLED:false}
- internal-prefix: ${APP_FILESERVER_PLATFORM_NGINX_INTERNAL_PREFIX:/__files/}
- object-suffix: ${APP_FILESERVER_PLATFORM_NGINX_OBJECT_SUFFIX:.bin}
- minimum-size: ${APP_FILESERVER_PLATFORM_NGINX_MINIMUM_SIZE:16MB}
- observability:
- metrics-enabled: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_METRICS_ENABLED:true}
- # Secret. Keyed HMAC over file identifiers; an unkeyed digest of an enumerable id is
- # reversible, so startup fails while metrics are on and this is blank.
- fingerprint-key: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_FINGERPRINT_KEY:}
- rate-limit:
- # Inbound HTTP enforcement is a separate axis from outbound provider activation.
- # enabled=true with no exact EdgeRateLimitPort fails fast; it never installs a local fallback.
- enabled: ${APP_RATE_LIMIT_ENABLED:false}
- default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default}
- hash-key-version: ${APP_RATE_LIMIT_HASH_KEY_VERSION:1}
- caller-deadline-budget: 2s
- # remote-addr-only | forwarded-headers-trusted (trusted ingress only)
- client-ip-mode: ${APP_RATE_LIMIT_CLIENT_IP_MODE:remote-addr-only}
- # Logical-cache-name → backendId routing (CacheStoreRouter). No keys by default —
- # forks add e.g. `bindings: { worklog: redis }` or env APP_CACHE_BINDINGS_WORKLOG=redis.
- # A binding to a backend that is not enabled fails startup (Layer 3 moved to router).
- messaging:
- # true | false — whether there is a broker at all. `broker` below selects which one; a
- # blank broker used to be the de-facto switch, which is a selector doing a switch's job.
- enabled: ${APP_MESSAGING_ENABLED:false}
- # Active message broker id (e.g. kafka). Blank = messaging disabled (fail-fast on use).
- # Selects the single MessageBroker; adding a broker is new files only (MessagingConfig).
- broker: ${APP_MESSAGING_BROKER:}
- kafka:
- # CSV of host:port; required (non-empty) only when broker=kafka. Bound +
- # validated by KafkaAdapterSettings (format) + KafkaAdapterConfig (required-when-active).
- brokers: ${APP_MESSAGING_KAFKA_BROKERS:}
- notification:
- # Active provider id per kind; blank = that kind disabled (fail-fast on use).
- # Add a provider = new files only (NotificationConfig); select it here.
- # This is the R0 legacy seam. The delivery platform is the separate
- # ca-skeleton.notification.platform tree below.
- slack:
- provider: ${APP_NOTIFICATION_SLACK_PROVIDER:}
- email:
- provider: ${APP_NOTIFICATION_EMAIL_PROVIDER:}
-
-backend:
- graphql:
- # true | false — publishes /graphql, including the route Spring GraphQL would publish itself.
- enabled: ${APP_GRAPHQL_ENABLED:false}
- # Required when the switch above is true, and must match the runtime environment
- # (local->LOCAL, dev->DEV, prod->PRODUCTION_INTERNAL|PRODUCTION_PUBLIC). Replaces the
- # production boolean and environment enum, which could disagree with each other.
- deployment-mode: ${APP_GRAPHQL_DEPLOYMENT_MODE:}
+ config:
+ import:
+ - classpath:config/server.yml
+ - classpath:config/observability.yml
+ - classpath:config/persistence.yml
+ - classpath:config/capabilities.yml
+ - classpath:config/runtime.yml
+ - classpath:config/notification.yml
+ - classpath:config/idempotency.yml
+ - classpath:config/outbox.yml
+ - classpath:config/security.yml
+ - classpath:config/redis.yml
+ - classpath:config/fileserver.yml
+ - classpath:config/httpclient.yml
+ - classpath:config/ratelimit.yml
+ - classpath:config/messaging.yml
+ - classpath:config/graphql.yml
diff --git a/src/app-bootstrap/src/main/resources/config/capabilities.yml b/src/app-bootstrap/src/main/resources/config/capabilities.yml
new file mode 100644
index 00000000..e2dc450f
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/capabilities.yml
@@ -0,0 +1,71 @@
+# 능력별 제공자 선택(cache/rate-limit/lease/idempotency).
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+ca-skeleton:
+ capabilities:
+ # Every capability below renders its keys under one namespace — app.redis.namespace, which is
+ # {environment}:{service}:{domain}. The per-capability namespace-application /
+ # namespace-environment pairs that used to live here are gone: four capabilities each joining
+ # two free-form tokens in their own order produced four different key prefixes, and the ACL
+ # pattern meant to fence the deployment in matched none of them.
+ cache:
+ # Canonical semantic cache activation. Disabled by default; "redis" composes
+ # RedisCacheRegionAdapter for the region named below and resolves HMAC material by reference.
+ bindings:
+ default: ${APP_CACHE_CANONICAL_DEFAULT_PROVIDER:disabled}
+ semantic-region: ${APP_CACHE_REDIS_SEMANTIC_REGION:default}
+ key-version: 1
+ key-hmac-secret-reference: secret://environment/APP_CACHE_REDIS_KEY_HMAC_SECRET
+ command-timeout: ${APP_CACHE_REDIS_COMMAND_TIMEOUT:200ms}
+ positive-soft-ttl: ${APP_CACHE_REDIS_POSITIVE_SOFT_TTL:30s}
+ positive-hard-ttl: ${APP_CACHE_REDIS_POSITIVE_HARD_TTL:5m}
+ negative-ttl: ${APP_CACHE_REDIS_NEGATIVE_TTL:10s}
+ # Floor on the hard TTL. Below it, entries expire faster than the round trip that wrote them:
+ # every read misses and every miss writes.
+ minimum-hard-ttl: ${APP_CACHE_REDIS_MINIMUM_HARD_TTL:1s}
+ idempotency:
+ # disabled | jdbc | redis. JDBC is the existing V1 provider; Redis is the owner-safe V2
+ # provider. They are mutually exclusive and no V1-to-V2 facade is inferred.
+ provider: ${APP_IDEMPOTENCY_PROVIDER:disabled}
+ key-hmac-secret-reference: secret://environment/APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET
+ key-version: 1
+ command-timeout: ${APP_IDEMPOTENCY_REDIS_COMMAND_TIMEOUT:200ms}
+ processing-lease: ${APP_IDEMPOTENCY_PROCESSING_LEASE:30s}
+ replay-ttl: ${APP_IDEMPOTENCY_TTL:24h}
+ failure-retention: ${APP_IDEMPOTENCY_FAILURE_RETENTION:24h}
+ response-codec-id: json-v2
+ # Numeric: the owner-safe claim writes this into the record and compares it there.
+ policy-revision: 2
+ lease:
+ # disabled | redis. This is EFFICIENCY_ONLY and never supplies fencing.
+ provider: ${APP_LEASE_PROVIDER:disabled}
+ key-hmac-secret-reference: secret://environment/APP_LEASE_REDIS_KEY_HMAC_SECRET
+ key-version: 1
+ command-timeout: ${APP_LEASE_REDIS_COMMAND_TIMEOUT:200ms}
+ contention-retry-after: ${APP_LEASE_REDIS_CONTENTION_RETRY_AFTER:50ms}
+ # How much shorter than the server's TTL this holder considers its lease valid. The two
+ # clocks are not the same clock, and a holder that measured the full TTL locally would still
+ # believe it held a lease the server had already handed to somebody else.
+ drift-budget: ${APP_LEASE_REDIS_DRIFT_BUDGET:10ms}
+ rate-limit:
+ # disabled | redis. This is the sole outbound provider activation selector.
+ provider: ${APP_RATE_LIMIT_PROVIDER:disabled}
+ failure-policy: ${APP_RATE_LIMIT_FAILURE_POLICY:fail-closed}
+ default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default}
+ failure-retry-after: ${APP_RATE_LIMIT_FAILURE_RETRY_AFTER:100ms}
+ key-version: ${APP_RATE_LIMIT_KEY_VERSION:1}
+ command-timeout: ${APP_RATE_LIMIT_REDIS_COMMAND_TIMEOUT:200ms}
+ policies:
+ api-default:
+ revision: ${APP_RATE_LIMIT_POLICY_REVISION:v1}
+ algorithm: ${APP_RATE_LIMIT_ALGORITHM:sliding-counter}
+ limit: ${APP_RATE_LIMIT_LIMIT:100}
+ window: ${APP_RATE_LIMIT_WINDOW:1s}
+ capacity: ${APP_RATE_LIMIT_CAPACITY:100}
+ refill-tokens: ${APP_RATE_LIMIT_REFILL_TOKENS:100}
+ refill-period: ${APP_RATE_LIMIT_REFILL_PERIOD:1s}
+ maximum-cost: ${APP_RATE_LIMIT_MAXIMUM_COST:10}
+ cleanup-grace: ${APP_RATE_LIMIT_CLEANUP_GRACE:5s}
+ maximum-clock-regression: ${APP_RATE_LIMIT_MAXIMUM_CLOCK_REGRESSION:250ms}
+ security:
diff --git a/src/app-bootstrap/src/main/resources/config/fileserver.yml b/src/app-bootstrap/src/main/resources/config/fileserver.yml
new file mode 100644
index 00000000..5be8547c
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/fileserver.yml
@@ -0,0 +1,127 @@
+# 파일 전송 경계와 Fileserver 플랫폼.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+app:
+ fileserver:
+ enabled: ${APP_FILESERVER_ENABLED:false}
+ destinations:
+ local-export:
+ provider-ref: local-primary
+ required-publication: unique-atomic-create
+ required-durability: file-and-directory-sync
+ maximum-rows: 1000000
+ maximum-encoded-bytes: 1073741824
+ providers:
+ local-primary:
+ # local-persistent is the only implemented/qualified R2 provider.
+ # shared-mounted/NFS and SFTP settings must not be added before their providers exist.
+ type: local-persistent
+ root-directory: ${APP_FILESERVER_LOCAL_ROOT:}
+ auto-create: false
+ strict-path-security: true
+ expected-file-store-name: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME:}
+ expected-file-store-type: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE:}
+ mount-sentinel-name: .ca-fileserver-volume
+ mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:}
+ expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:}
+ maximum-root-mode: "0750"
+ # HTTP Fileserver platform — a different capability from app.fileserver above, which publishes
+ # tabular exports. This one serves upload/download/lifecycle routes over HTTP and owns its own
+ # namespace so the two cannot be switched on by accident together.
+ #
+ # While enabled=false none of the detail below is bound: FileserverPlatformAutoConfiguration
+ # binds the block itself, and it is not processed until the master switch is true. Unknown keys
+ # under this prefix are refused rather than ignored.
+ # Outbound HTTP Client platform. Only the master switch lives here.
+ #
+ # The per-client surface is an indexed list, and templating one element would materialise a
+ # nameless client in every deployment — which the settings' own aggregate validation refuses, so
+ # the template could not be left in place. Clients are therefore declared straight from the
+ # environment as APP_HTTPCLIENT_CLIENTS_0_*, registered in
+ # docs/httpclient/env-fields.yaml, and an APP_HTTPCLIENT_ variable absent from that
+ # registry fails startup rather than being ignored.
+ #
+ # While enabled=false none of it is bound: HttpClientPlatformAutoConfiguration binds the block
+ # itself and is not processed until the master switch is true.
+ fileserver-platform:
+ enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false}
+ # Writer-lease owner. Must be unique per instance in a multi-instance deployment; the startup
+ # gate treats the default as single-instance.
+ instance-id: ${APP_FILESERVER_PLATFORM_INSTANCE_ID:local-node}
+ default-namespace: ${APP_FILESERVER_PLATFORM_DEFAULT_NAMESPACE:default}
+ storage:
+ # Absolute, on its own volume, and never under a web or configuration root. A relative path
+ # resolves against the process working directory, which differs between a container and a
+ # test, so it is refused.
+ root: ${APP_FILESERVER_PLATFORM_STORAGE_ROOT:/var/lib/backend/files}
+ publish-mode: ${APP_FILESERVER_PLATFORM_STORAGE_PUBLISH_MODE:atomic-move-preferred}
+ buffer-size: ${APP_FILESERVER_PLATFORM_STORAGE_BUFFER_SIZE:128KB}
+ forbidden-root-ancestors: ${APP_FILESERVER_PLATFORM_STORAGE_FORBIDDEN_ROOT_ANCESTORS:/app,/etc,/usr/share/nginx/html}
+ upload:
+ # Shared with spring.servlet.multipart below through the same placeholder. Two independent
+ # limits would let the servlet container reject an upload the Fileserver policy allows,
+ # before any Fileserver code — including its error mapping — ever runs.
+ max-file-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_FILE_SIZE:100MB}
+ max-request-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_REQUEST_SIZE:110MB}
+ initial-reservation: ${APP_FILESERVER_PLATFORM_UPLOAD_INITIAL_RESERVATION:8MB}
+ max-parts: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_PARTS:16}
+ ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_TTL:1h}
+ reservation-ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_RESERVATION_TTL:24h}
+ lease-duration: ${APP_FILESERVER_PLATFORM_UPLOAD_LEASE_DURATION:30s}
+ require-content-length: ${APP_FILESERVER_PLATFORM_UPLOAD_REQUIRE_CONTENT_LENGTH:false}
+ download:
+ cache-control: ${APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL:private, no-store}
+ inline-allowed: ${APP_FILESERVER_PLATFORM_DOWNLOAD_INLINE_ALLOWED:false}
+ max-ranges: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGES:1}
+ # Applies to the single-range profile too, so the ceiling is not inert by default.
+ max-range-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGE_BYTES:100MB}
+ zero-copy-enabled: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_ENABLED:true}
+ zero-copy-minimum-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_MINIMUM_BYTES:16MB}
+ transfer:
+ core-size: ${APP_FILESERVER_PLATFORM_TRANSFER_CORE_SIZE:8}
+ max-size: ${APP_FILESERVER_PLATFORM_TRANSFER_MAX_SIZE:32}
+ queue-capacity: ${APP_FILESERVER_PLATFORM_TRANSFER_QUEUE_CAPACITY:64}
+ await-seconds: ${APP_FILESERVER_PLATFORM_TRANSFER_AWAIT_SECONDS:300}
+ security:
+ # required | role-based | unenforced. `required` has no built-in policy and fails startup
+ # unless the deployment supplies a FileAccessPolicy bean; `unenforced` is refused under a
+ # production profile.
+ access-policy: ${APP_FILESERVER_PLATFORM_SECURITY_ACCESS_POLICY:required}
+ read-roles: ${APP_FILESERVER_PLATFORM_SECURITY_READ_ROLES:ROLE_FILE_READ}
+ write-roles: ${APP_FILESERVER_PLATFORM_SECURITY_WRITE_ROLES:ROLE_FILE_WRITE}
+ admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN}
+ verification:
+ timeout: ${APP_FILESERVER_PLATFORM_VERIFICATION_TIMEOUT:5s}
+ require-media-type-verdict: ${APP_FILESERVER_PLATFORM_VERIFICATION_REQUIRE_MEDIA_TYPE_VERDICT:false}
+ inline-safe-profile: ${APP_FILESERVER_PLATFORM_VERIFICATION_INLINE_SAFE_PROFILE:false}
+ quota:
+ instance-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_INSTANCE_UPLOAD_PERMITS:16}
+ scope-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_SCOPE_UPLOAD_PERMITS:4}
+ direct-download-permits: ${APP_FILESERVER_PLATFORM_QUOTA_DIRECT_DOWNLOAD_PERMITS:64}
+ soft-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_SOFT_HIGH_WATER:0.70}
+ hard-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_HARD_HIGH_WATER:0.85}
+ admin:
+ enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false}
+ orphan-minimum-age: ${APP_FILESERVER_PLATFORM_ADMIN_ORPHAN_MINIMUM_AGE:1h}
+ cleanup:
+ enabled: ${APP_FILESERVER_PLATFORM_CLEANUP_ENABLED:false}
+ interval: ${APP_FILESERVER_PLATFORM_CLEANUP_INTERVAL:60s}
+ max-items: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_ITEMS:100}
+ max-bytes: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_BYTES:1GB}
+ retry-backoff: ${APP_FILESERVER_PLATFORM_CLEANUP_RETRY_BACKOFF:5m}
+ tus:
+ enabled: ${APP_FILESERVER_PLATFORM_TUS_ENABLED:false}
+ httpbis-draft12:
+ # Unratified protocol; the contract can change without notice.
+ enabled: ${APP_FILESERVER_PLATFORM_HTTPBIS_DRAFT12_ENABLED:false}
+ nginx:
+ enabled: ${APP_FILESERVER_PLATFORM_NGINX_ENABLED:false}
+ internal-prefix: ${APP_FILESERVER_PLATFORM_NGINX_INTERNAL_PREFIX:/__files/}
+ object-suffix: ${APP_FILESERVER_PLATFORM_NGINX_OBJECT_SUFFIX:.bin}
+ minimum-size: ${APP_FILESERVER_PLATFORM_NGINX_MINIMUM_SIZE:16MB}
+ observability:
+ metrics-enabled: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_METRICS_ENABLED:true}
+ # Secret. Keyed HMAC over file identifiers; an unkeyed digest of an enumerable id is
+ # reversible, so startup fails while metrics are on and this is blank.
+ fingerprint-key: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_FINGERPRINT_KEY:}
diff --git a/src/app-bootstrap/src/main/resources/config/graphql.yml b/src/app-bootstrap/src/main/resources/config/graphql.yml
new file mode 100644
index 00000000..a3a1d550
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/graphql.yml
@@ -0,0 +1,12 @@
+# GraphQL 트랜스포트 스위치.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+backend:
+ graphql:
+ # true | false — publishes /graphql, including the route Spring GraphQL would publish itself.
+ enabled: ${APP_GRAPHQL_ENABLED:false}
+ # Required when the switch above is true, and must match the runtime environment
+ # (local->LOCAL, dev->DEV, prod->PRODUCTION_INTERNAL|PRODUCTION_PUBLIC). Replaces the
+ # production boolean and environment enum, which could disagree with each other.
+ deployment-mode: ${APP_GRAPHQL_DEPLOYMENT_MODE:}
diff --git a/src/app-bootstrap/src/main/resources/config/httpclient.yml b/src/app-bootstrap/src/main/resources/config/httpclient.yml
new file mode 100644
index 00000000..980c9386
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/httpclient.yml
@@ -0,0 +1,7 @@
+# 아웃바운드 HTTP 클라이언트 플랫폼 스위치. 나머지 키는 환경변수에서 직접 바인딩된다.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+app:
+ httpclient:
+ enabled: ${APP_HTTPCLIENT_ENABLED:false}
diff --git a/src/app-bootstrap/src/main/resources/config/idempotency.yml b/src/app-bootstrap/src/main/resources/config/idempotency.yml
new file mode 100644
index 00000000..7d3209b5
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/idempotency.yml
@@ -0,0 +1,10 @@
+# 멱등성 TTL 상한.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+ca-skeleton:
+ idempotency:
+ # feature-rate-limit-idempotency-contract D6/§E. ttl is env-driven (<=72h,
+ # validated in IdempotencyProperties); reaper-interval is literal operational tuning.
+ ttl: ${APP_IDEMPOTENCY_TTL:24h}
+ reaper-interval: 10m
diff --git a/src/app-bootstrap/src/main/resources/config/messaging.yml b/src/app-bootstrap/src/main/resources/config/messaging.yml
new file mode 100644
index 00000000..2d752237
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/messaging.yml
@@ -0,0 +1,16 @@
+# 벤더드 메시징 플랫폼 스위치.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+app:
+ messaging:
+ # true | false — whether there is a broker at all. `broker` below selects which one; a
+ # blank broker used to be the de-facto switch, which is a selector doing a switch's job.
+ enabled: ${APP_MESSAGING_ENABLED:false}
+ # Active message broker id (e.g. kafka). Blank = messaging disabled (fail-fast on use).
+ # Selects the single MessageBroker; adding a broker is new files only (MessagingConfig).
+ broker: ${APP_MESSAGING_BROKER:}
+ kafka:
+ # CSV of host:port; required (non-empty) only when broker=kafka. Bound +
+ # validated by KafkaAdapterSettings (format) + KafkaAdapterConfig (required-when-active).
+ brokers: ${APP_MESSAGING_KAFKA_BROKERS:}
diff --git a/src/app-bootstrap/src/main/resources/config/notification.yml b/src/app-bootstrap/src/main/resources/config/notification.yml
new file mode 100644
index 00000000..33d46077
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/notification.yml
@@ -0,0 +1,145 @@
+# 알림 전송 플랫폼과 R0 레거시 제공자 선택. 두 트리가 한 파일에 있는 것은 의도다 — 운영자가 '알림'을 찾을 때 한 곳만 보면 된다.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+ca-skeleton:
+ notification:
+ platform:
+ # The master switch. False binds nothing at all.
+ enabled: ${APP_NOTIFICATION_PLATFORM_ENABLED:false}
+ # SERVING | INGEST_ONLY. SERVING refuses to start without a working provider; INGEST_ONLY
+ # stores requests and does not dispatch, which is the shape a migration or a drain wants.
+ mode: ${APP_NOTIFICATION_PLATFORM_MODE:SERVING}
+ dispatch:
+ claim-batch-size: ${APP_NOTIFICATION_PLATFORM_CLAIM_BATCH_SIZE:50}
+ # The lease must outlast a provider call plus its timeout, or a live worker's delivery is
+ # claimed by a second one while the first is still waiting on the provider.
+ lease-duration: ${APP_NOTIFICATION_PLATFORM_LEASE_DURATION:2m}
+ poll-interval: ${APP_NOTIFICATION_PLATFORM_POLL_INTERVAL:1s}
+ max-global-concurrency: ${APP_NOTIFICATION_PLATFORM_MAX_CONCURRENCY:64}
+ max-additional-attempts: ${APP_NOTIFICATION_PLATFORM_MAX_ADDITIONAL_ATTEMPTS:4}
+ max-queue-age: ${APP_NOTIFICATION_PLATFORM_MAX_QUEUE_AGE:24h}
+ # An ambiguous attempt means the request reached the provider and the outcome could not be
+ # read. Falling back would risk sending the same notification twice, so it is off.
+ allow-ambiguous-fallback: ${APP_NOTIFICATION_PLATFORM_ALLOW_AMBIGUOUS_FALLBACK:false}
+ callbacks:
+ enabled: ${APP_NOTIFICATION_PLATFORM_CALLBACKS_ENABLED:false}
+ # Ceiling is 65508 = the ciphertext column minus the AES-GCM nonce and tag. A larger value
+ # is refused at binding rather than at the database, after the callback was acknowledged.
+ max-body-bytes: ${APP_NOTIFICATION_PLATFORM_CALLBACK_MAX_BODY_BYTES:65508}
+ replay-skew: ${APP_NOTIFICATION_PLATFORM_CALLBACK_REPLAY_SKEW:5m}
+ # Peers whose Forwarded/X-Forwarded-* headers may be believed when reconstructing the URL a
+ # provider signed. Empty by default, and that is the safe default rather than the convenient
+ # one: with no entry the resolver uses what the container observed, so no caller can choose
+ # the URL its own signature is checked against. A deployment behind a load balancer lists its
+ # proxies here.
+ trusted-proxies: ${APP_NOTIFICATION_PLATFORM_CALLBACK_TRUSTED_PROXIES:}
+ # Provider profiles, keyed by profile id. None by default: an empty map with mode=SERVING is
+ # refused at startup, which is the intended outcome for "enabled but not configured".
+ #
+ # A profile looks like this — copy it under a profile id of your own and supply the secrets
+ # through the credential profile, never inline:
+ #
+ # providers:
+ # ses-primary:
+ # type: SES # APNS | FCM | SES | SMTP | TWILIO | WEB_PUSH | WEBHOOK
+ # enabled: true
+ # primary-for-channel: true # exactly one primary per channel
+ # environment: PRODUCTION
+ # credential-profile: ses-prod # resolved through SecretMaterialProvider
+ # timeout: 10s
+ # max-concurrency: 16
+ # rate-per-second: 50
+ # One shipped profile, disabled, because a map cannot be written by an environment variable.
+ #
+ # `ca-skeleton` contains a hyphen and an environment variable cannot express one — Spring maps
+ # `_` to `.`, never to `-` — so every setting in this tree reaches a deployment through an
+ # APP_ placeholder named here. A provider map with caller-chosen keys has no placeholder to be
+ # named by, which is why the tree shipped as `providers: {}` and no deployment could add one.
+ #
+ # So the template ships the reference family as one profile, off. A deployment that wants a
+ # different id or a second family declares it in its own YAML; a deployment that wants the
+ # common case sets APP_NOTIFICATION_PLATFORM_SMTP_ENABLED=true and the relay address below.
+ #
+ # Disabled profiles are skipped by assembly before their type is resolved, so this costs a
+ # deployment that does not want it exactly nothing.
+ providers:
+ smtp:
+ type: SMTP
+ enabled: ${APP_NOTIFICATION_PLATFORM_SMTP_ENABLED:false}
+ # Exactly one primary per channel; a second primary for EMAIL fails assembly by name.
+ primary-for-channel: ${APP_NOTIFICATION_PLATFORM_SMTP_PRIMARY:true}
+ environment: ${APP_NOTIFICATION_PLATFORM_SMTP_ENVIRONMENT:local}
+ credential-profile: ${APP_NOTIFICATION_PLATFORM_SMTP_CREDENTIAL_PROFILE:default}
+ timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_TIMEOUT:10s}
+ max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_MAX_CONCURRENCY:4}
+ rate-per-second: ${APP_NOTIFICATION_PLATFORM_SMTP_RATE_PER_SECOND:10}
+ # What the relay is, as opposed to which profile uses it. Host, port and credentials stay
+ # spring.mail.*; these are the parts Spring's mail properties have no word for.
+ smtp:
+ # STARTTLS_REQUIRED | IMPLICIT_TLS. There is no plaintext member: the type refuses an
+ # unencrypted relay by construction rather than by a validator somebody has to run.
+ tls-mode: ${APP_NOTIFICATION_PLATFORM_SMTP_TLS_MODE:STARTTLS_REQUIRED}
+ # example.invalid is reserved by RFC 2606 and resolves nowhere, so a deployment that forgot
+ # to set one gets a traceable bounce rather than mail apparently from an address it does not
+ # own.
+ sender-identity: ${APP_NOTIFICATION_PLATFORM_SMTP_SENDER_IDENTITY:no-reply@example.invalid}
+ connect-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_CONNECT_TIMEOUT:5s}
+ read-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_READ_TIMEOUT:10s}
+ write-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_WRITE_TIMEOUT:10s}
+ max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_DISPATCH_CONCURRENCY:4}
+ # ---- Key material -------------------------------------------------------
+ # Eight purposes, eight keys, each base64 of at least 32 bytes and each distinct from every
+ # other. The platform decodes them at startup and refuses to boot if one is blank, short or
+ # shared: a blank value used to be skipped, so the platform started without the key and found
+ # out on the first contact point — in production, on a user's notification.
+ #
+ # These were bound by nothing until now. The subtree was missing from this file entirely while
+ # the settings record and its validator existed, so `enabled: true` was an unconditional boot
+ # failure in every environment and the only way to discover the eight names was to read the
+ # record. That is the gap this block closes; the empty defaults keep a deployment with the
+ # platform off from having to supply anything.
+ #
+ # Never write a value here. Supply them out of band, per environment.
+ secrets:
+ contact-encryption-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY:}
+ contact-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY:}
+ callback-signing-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY:}
+ provider-credential-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY:}
+ payload-encryption-key: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY:}
+ vapid-signing-key: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY:}
+ provider-request-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY:}
+ callback-fingerprint-hmac-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY:}
+ # The id written into every envelope this key produces, one per purpose, keyed by the
+ # purpose's own name. Not secret — an id identifies key material without revealing it — but
+ # required all the same, and with no default on purpose: a constant id makes a rotation
+ # indistinguishable from the key it replaced, so nothing could decrypt what was written
+ # before it. Change the id in the same deployment that changes the material.
+ active-key-ids:
+ CONTACT_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY_ID:}
+ CONTACT_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY_ID:}
+ CALLBACK_SIGNING: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY_ID:}
+ PROVIDER_CREDENTIAL: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY_ID:}
+ PAYLOAD_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY_ID:}
+ VAPID_SIGNING: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY_ID:}
+ PROVIDER_REQUEST_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY_ID:}
+ CALLBACK_FINGERPRINT_HMAC: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY_ID:}
+ # Superseded material, kept readable, grouped by the purpose it served and then by the id
+ # it was active under — historical-keys.PAYLOAD_ENCRYPTION.payload-2026-01. The purpose is
+ # not recoverable from an id, and every consumer that resolves a key by id refuses one
+ # carrying the wrong purpose, so a retired key filed under the wrong heading resolves and
+ # is then rejected. Unset until a first rotation; a deployment that has never rotated needs
+ # none. See docs/notification/at-rest-threat-model.md for the rotation sequence.
+ historical-keys: {}
+
+app:
+ notification:
+ # Active provider id per kind; blank = that kind disabled (fail-fast on use).
+ # Add a provider = new files only (NotificationConfig); select it here.
+ # This is the R0 legacy seam. The delivery platform is the separate
+ # ca-skeleton.notification.platform tree below.
+ slack:
+ provider: ${APP_NOTIFICATION_SLACK_PROVIDER:}
+ email:
+ provider: ${APP_NOTIFICATION_EMAIL_PROVIDER:}
+
diff --git a/src/app-bootstrap/src/main/resources/config/observability.yml b/src/app-bootstrap/src/main/resources/config/observability.yml
new file mode 100644
index 00000000..7c500b4a
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/observability.yml
@@ -0,0 +1,175 @@
+# 액추에이터 노출면, OpenAPI, 로그 레벨, 로깅/프라이버시/트레이싱 설정.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+# ---------------------------------------------------------------------------
+# Actuator / management — MERGED ownership at integration:
+# - feature-management-actuator-security-contract owns server.port, the exposure
+# allowlist/exclude, health show-details, shutdown-disabled, and info.
+# - feature-runtime-health-lifecycle-contract owns the health probe SHAPE
+# (probes.enabled + the liveness/readiness/startup groups + dependency taxonomy).
+# ---------------------------------------------------------------------------
+# ---------------------------------------------------------------------------
+# OpenAPI. Stated rather than defaulted: SpringDoc logs a startup WARN telling every deployment to
+# decide this, and a warning that appears on every start is one nobody reads. The template decides —
+# on where it is useful, off under prod, which application-prod.yml pins so the choice cannot be
+# widened by an environment variable in the one runtime where publishing the whole API shape is a
+# reconnaissance surface.
+# ---------------------------------------------------------------------------
+springdoc:
+ api-docs:
+ enabled: ${APP_OPENAPI_DOCS_ENABLED:true}
+ swagger-ui:
+ enabled: ${APP_OPENAPI_UI_ENABLED:true}
+
+management:
+ # feature-management-actuator-security-contract D1: separate management port so actuator
+ # endpoints are not exposed on the same socket as the app API. Default: 9001.
+ server:
+ port: ${MANAGEMENT_SERVER_PORT:9001}
+ endpoints:
+ web:
+ # D2: production allowlist — only safe, scrape-friendly endpoints are exposed.
+ exposure:
+ # adapteractivation reports which master switches resolved on and what that makes required.
+ # It reads the environment and creates nothing, so it leaks no secret and opens no resource —
+ # and it is exposed precisely so a smoke lane can check the application's own answer rather
+ # than the flags the lane itself passed in, which is a test of the test.
+ include: health,prometheus,info,loggers,adapteractivation
+ # D2/D4/D5: explicitly excluded dangerous endpoints (env leaks secrets,
+ # heapdump/threaddump = memory forensics, shutdown = remote kill, configprops = secret leak).
+ exclude: env,configprops,heapdump,threaddump,shutdown
+ endpoint:
+ health:
+ # D8: never expose health details to unauthenticated callers.
+ show-details: when-authorized
+ # Membership is validated. With validation off, a group naming a contributor that does not
+ # exist is silently dropped and readiness reports UP while proving nothing about the
+ # dependency it claims to gate on — a false green that survives exactly as long as nobody
+ # checks. On, the cost is that every name below must resolve, in every deployment.
+ #
+ # That cost is paid by construction rather than by convention: the groups below name only
+ # unconditional contributors, and the one conditional member — `redisRequired` — is appended
+ # by RedisReadinessGroupPostProcessor from the same predicate that creates the bean. A
+ # contributor can therefore be named only where it exists, and a misspelled one still fails
+ # startup.
+ validate-group-membership: true
+ # feature-runtime-health-lifecycle-contract: expose the Kubernetes-ready
+ # liveness/readiness/startup probe paths.
+ probes:
+ enabled: true
+ group:
+ # Liveness: JVM can continue (OOM → liveness DOWN → pod restart).
+ # Only livenessState is included; dependency health MUST NOT be here —
+ # a DOWN DB must never trigger a pod restart when the JVM is healthy.
+ liveness:
+ include: livenessState
+ # Readiness: ready to serve traffic AND all REQUIRED dependencies up.
+ # Every name here must resolve to a contributor that exists (validate-group-membership
+ # above). A correctness-role Redis contributor belongs in this list and is added when the
+ # Redis runtime composition creates one; naming it before then produced a readiness probe
+ # that reported UP without ever checking Redis.
+ # `redisRequired` is NOT listed here, and must not be: group membership does not tolerate a
+ # conditional contributor being absent. Boot validates the group against the contributors
+ # that exist, so naming it statically made every Redis-off and cache-only deployment fail at
+ # startup with "Included health contributor 'redisRequired' in group 'readiness' does not
+ # exist". RedisReadinessGroupPostProcessor appends it to this list — from the same predicate
+ # that creates the bean — when Redis is on and a correctness role selected it.
+ # `redisOptional` is never appended: a cache outage is reported as degraded detail and never
+ # turns a healthy JVM or an otherwise-ready pod unavailable.
+ readiness:
+ include: readinessState
+ # Startup: startup/migration validation complete.
+ # readinessState acts as the startup completion gate — it flips UP only
+ # after the context is fully initialized (Flyway migration included).
+ startup:
+ include: readinessState
+ shutdown:
+ # D4: shutdown endpoint disabled globally — even if somehow exposed, it cannot be invoked.
+ access: none
+ info:
+ build:
+ # Build-info only (no env leak).
+ enabled: true
+
+logging:
+ level:
+ # TRACE | DEBUG | INFO | WARN | ERROR | OFF
+ #
+ # Inline defaults, because these had none and only application-local.yml pinned them. That made
+ # local the one environment that could start without an operator supplying every level: dev and
+ # prod failed to bind `logging.level.root` before reaching any of their own settings, which is a
+ # log-configuration problem wearing a startup failure's clothes.
+ root: ${APP_LOG_LEVEL_ROOT:INFO}
+ dev.caskeleton: ${APP_LOG_LEVEL_APP:INFO}
+ org.springframework: ${APP_LOG_LEVEL_SPRING:INFO}
+ org.springframework.web: ${APP_LOG_LEVEL_WEB:INFO}
+ # DEBUG here prints SQL once JPA/jdbc is wired in
+ org.hibernate.SQL: ${APP_LOG_LEVEL_SQL:INFO}
+
+# Module-scoped knobs. Each block is bound into a *Settings @ConfigurationProperties
+# record in the corresponding module, which is where allowed-value validation lives.
+ca-skeleton:
+ logging:
+ file:
+ # true | false (wraps console + adds rolling JSON file appender)
+ enabled: ${APP_LOG_FILE_ENABLED:false}
+ # relative (to src/) or absolute
+ path: ${APP_LOG_FILE_PATH:logs/ca-skeleton.json}
+ # size with unit: KB | MB | GB
+ max-size: ${APP_LOG_FILE_MAX_SIZE:100MB}
+ # integer >= 1
+ max-history: ${APP_LOG_FILE_MAX_HISTORY:14}
+ # size with unit or 0
+ total-size-cap: ${APP_LOG_FILE_TOTAL_SIZE_CAP:3GB}
+ async:
+ # true | false (AsyncAppender wrapper for non-blocking I/O)
+ enabled: ${APP_LOG_ASYNC_ENABLED:true}
+ # integer >= 1
+ queue-size: ${APP_LOG_ASYNC_QUEUE_SIZE:512}
+ # integer >= 0 (0 = never drop)
+ discarding-threshold: ${APP_LOG_ASYNC_DISCARDING_THRESHOLD:20}
+ json:
+ # IANA timezone (UTC | Asia/Seoul | ...) or "default"
+ timezone: ${APP_LOG_JSON_TIMEZONE:UTC}
+ # ISO 8601 pattern
+ timestamp-pattern: ${APP_LOG_JSON_TIMESTAMP_PATTERN:yyyy-MM-dd'T'HH:mm:ss.SSSXXX}
+ # true | false (file/method/line — expensive)
+ include-caller-data: ${APP_LOG_JSON_INCLUDE_CALLER_DATA:false}
+ # integer; 0 = full name, positive = abbreviated
+ logger-name-length: ${APP_LOG_JSON_LOGGER_NAME_LENGTH:0}
+ # float in [0.0, 1.0] — keep-probability for <=INFO logs (prod 0.1 = 10% sampling;
+ # WARN/ERROR always 100%). Consumed by SamplingTurboFilter via logback .
+ sampling-rate: ${APP_LOG_SAMPLING_RATE:1.0}
+ privacy:
+ # secret-tier HMAC salt for user_principal pseudonymization (DRIFT-6; algorithm SSOT
+ # feature-data-retention-privacy-contract). Blank -> PrivacySettings warns + uses a dev
+ # sentinel; prod MUST supply a real secret-manager value.
+ pseudonymization-salt: ${APP_PRIVACY_PSEUDONYMIZATION_SALT:}
+ # feature-distributed-tracing-contract D1/D4/D6 — tracing seam settings.
+ # The OTel/Micrometer tracer runtime is active in this repo (Plan A — seam activated):
+ # micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp are wired in
+ # app-bootstrap/build.gradle. The exporter remains off while OTEL_EXPORTER_OTLP_ENDPOINT
+ # is blank (url_or_empty default). A fork can supply the endpoint via env or override
+ # SpanErrorRecorder with its own bean.
+ # D4 disabled-fallback: even when enabled=false, RequestLoggingFilter always
+ # generates a W3C traceparent so meta.traceId and log trace_id are never null.
+ # D6 per-profile defaults: prod=0.01 / staging=0.10 / dev·local=1.0.
+ # APP_TRACING_SAMPLE_RATE overrides the per-profile default when set.
+ tracing:
+ # true | false (boolean_strict). Tracing seam on/off.
+ enabled: ${APP_TRACING_ENABLED:true}
+ # float in [0.0, 1.0]. Per-profile override (D6 float_between_0_and_1 validation at startup).
+ # blank = per-profile default via TracingSampleRateResolver (D-1 ISSUE-1 fix).
+ sample-rate: ${APP_TRACING_SAMPLE_RATE:}
+ exporter:
+ # url_or_empty: blank = exporter off (D1 SEAM default); non-blank must be a valid URL.
+ otlp-endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:}
+
+# feature-integration-adapter-templates — optional integration adapter toggles.
+# The `app...enabled` flags feed Spring @ConditionalOnProperty (Layer 1):
+# disabled (default): messaging binds a fail-fast Disabled* sentinel bean; cache/notification
+# contribute nothing and fail fast in their router (CacheStoreRouter / RoutingNotifier) on
+# unbound access. Enabled registers the real adapter
+# (which needs its project-supplied integration client bean). Domain namespace, NOT a
+# generic `app.adapter.*` prefix (branch-note §Audit A1). Env keys are the registry SSOT.
diff --git a/src/app-bootstrap/src/main/resources/config/outbox.yml b/src/app-bootstrap/src/main/resources/config/outbox.yml
new file mode 100644
index 00000000..381b8628
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/outbox.yml
@@ -0,0 +1,30 @@
+# 트랜잭셔널 아웃박스 릴레이.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+ca-skeleton:
+ outbox:
+ # feature-domain-event-outbox-contract I11 — all six values are literal defaults;
+ # NO env placeholders (spec: 신규 env key 없음). Bound into OutboxProperties.
+ # true | false — enable/disable the relay scheduler (OutboxRelayScheduler).
+ # MSG-024: an enabled relay with a blank app.messaging.broker fails startup
+ # (OutboxRelayBrokerRequirementValidator). The relay would otherwise claim PENDING rows, fail
+ # every publish against the disabled publisher, and exhaust them to DEAD — a messaging-off
+ # environment must preserve its outbox, not destroy it one retry budget at a time.
+ # true | false — the capability switch. Off means no outbox settings, validator, leader token,
+ # metrics, or store binding at all; relay-enabled below only decides whether the scheduler runs.
+ # Conflating the two meant a relay-off deployment still assembled an OutboxMetrics over a store
+ # port that a database-less deployment does not have.
+ enabled: ${APP_OUTBOX_ENABLED:false}
+ relay-enabled: ${APP_OUTBOX_RELAY_ENABLED:false}
+ # ISO-8601 duration — how often the relay polls for pending rows
+ poll-interval: PT5S
+ # integer >= 1 — maximum rows claimed per relay cycle
+ batch-size: 20
+ # ISO-8601 duration — IN_FLIGHT orphan visibility window (I6: next_attempt_at reuse)
+ in-flight-timeout: PT5M
+ # ISO-8601 duration — read also by adapter-persistence OutboxReaper @Scheduled
+ reaper-interval: PT10M
+ # ISO-8601 duration — PUBLISHED row retention before reaper deletes them (I3)
+ # read also by adapter-persistence OutboxReaper via ${ca-skeleton.outbox.published-retention:P7D}
+ published-retention: P7D
diff --git a/src/app-bootstrap/src/main/resources/config/persistence.yml b/src/app-bootstrap/src/main/resources/config/persistence.yml
new file mode 100644
index 00000000..eef4eedd
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/persistence.yml
@@ -0,0 +1,41 @@
+# 관계형/문서 저장소 활성화와 JPA·persistence 정책.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+ca-skeleton:
+ # The five optional adapters ship in this artifact and are off unless a deployment says otherwise,
+ # so one jar serves every environment and an operator turns a capability on with one variable. Off
+ # is structural: no settings bind, no bean is created, no socket, pool or thread exists, and a
+ # malformed detail setting for a capability nobody enabled does not block startup.
+ # Registry SSOT: docs/registries/env-keys.yaml.
+ persistence-jpa:
+ # true | false — relational persistence: DataSource, entity scan, repositories, Flyway.
+ enabled: ${APP_PERSISTENCE_JPA_ENABLED:false}
+ persistence-mongo:
+ # true | false
+ enabled: ${APP_PERSISTENCE_MONGO_ENABLED:false}
+ # Required non-blank when the switch above is true. Exactly one profile: the runtime builds one
+ # sync client and one pool, and never resolves a non-selected profile's secret.
+ active-profile: ${APP_PERSISTENCE_MONGO_ACTIVE_PROFILE:}
+ # Canonical HTTP capability activation. Bindings are the sole activation SSOT: provider
+ # definitions alone are inert, and the current NOT_IMPLEMENTED readiness card rejects ACTIVE
+ # before any client/executor/pool resource can be created.
+ persistence:
+ vendor: postgresql
+ # JPA named-policy deadline envelope. JpaTransactionSettings validates the hierarchy;
+ # SpringPolicyTransactionPort intersects these limits with the caller's absolute CallBudget
+ # and the actual Hikari connection timeout before acquiring a transaction.
+ jpa:
+ transaction:
+ transaction-timeout: 30s
+ begin-budget: 250ms
+ minimum-action-window: 1s
+ completion-margin: 500ms
+ statement-timeout: 10s
+ lock-timeout: 2s
+ idle-guard-timeout: 15s
+ transaction-margin: 250ms
+ lock-margin: 100ms
+ retry-base-delay: 10ms
+ retry-maximum-delay: 50ms
+ retry-maximum-attempts: 2
diff --git a/src/app-bootstrap/src/main/resources/config/ratelimit.yml b/src/app-bootstrap/src/main/resources/config/ratelimit.yml
new file mode 100644
index 00000000..04b3efa4
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/ratelimit.yml
@@ -0,0 +1,17 @@
+# 엣지 레이트 리밋 정책.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+app:
+ rate-limit:
+ # Inbound HTTP enforcement is a separate axis from outbound provider activation.
+ # enabled=true with no exact EdgeRateLimitPort fails fast; it never installs a local fallback.
+ enabled: ${APP_RATE_LIMIT_ENABLED:false}
+ default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default}
+ hash-key-version: ${APP_RATE_LIMIT_HASH_KEY_VERSION:1}
+ caller-deadline-budget: 2s
+ # remote-addr-only | forwarded-headers-trusted (trusted ingress only)
+ client-ip-mode: ${APP_RATE_LIMIT_CLIENT_IP_MODE:remote-addr-only}
+ # Logical-cache-name → backendId routing (CacheStoreRouter). No keys by default —
+ # forks add e.g. `bindings: { worklog: redis }` or env APP_CACHE_BINDINGS_WORKLOG=redis.
+ # A binding to a backend that is not enabled fails startup (Layer 3 moved to router).
diff --git a/src/app-bootstrap/src/main/resources/config/redis.yml b/src/app-bootstrap/src/main/resources/config/redis.yml
new file mode 100644
index 00000000..31092396
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/redis.yml
@@ -0,0 +1,15 @@
+# Redis 전역 스위치와 SDK 네임스페이스.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+app:
+ # The one global Redis switch. False (the default) means no Redis settings are bound, no Redis
+ # credential is required, no client, connection, thread or health contributor is created, and no
+ # Redis-specific resource is read. There is deliberately no second master switch: a role such as
+ # cache or session selects *which* Redis capabilities compose, never *whether* Redis exists.
+ redis:
+ enabled: ${APP_REDIS_ENABLED:false}
+ # Fileserver R2 exact destination/provider composition. Disabled by default: while false,
+ # these blank attestation placeholders do not create directories, probe a filesystem, or
+ # contribute FilePublicationPort. Enabling fails closed unless every local-persistent
+ # attestation value matches the pre-provisioned root. No implicit local fallback exists.
diff --git a/src/app-bootstrap/src/main/resources/config/runtime.yml b/src/app-bootstrap/src/main/resources/config/runtime.yml
new file mode 100644
index 00000000..974ce6c9
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/runtime.yml
@@ -0,0 +1,65 @@
+# 부트스트랩 신원, 런타임 안전 토글, 분산 락, 표현 계층, 비동기 실행기.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+ca-skeleton:
+ bootstrap:
+ # required, non-blank — startup fails if blank (see BootstrapSettings)
+ app-name: ${APP_NAME}
+ runtime:
+ # feature-env-driven-runtime-configuration D8: enforced at startup by
+ # StartupSafetyValidator. The two prod-unsafe toggles fail startup if true under
+ # the prod profile; multi-instance fails startup if its coordination beans are absent.
+ # true | false (internal error detail in responses — forbidden under prod)
+ error-detail-exposure-enabled: ${APP_ERROR_DETAIL_EXPOSURE_ENABLED:false}
+ # true | false (request/response body capture in logs — forbidden under prod)
+ log-body-capture-enabled: ${APP_LOG_BODY_CAPTURE_ENABLED:false}
+ # true | false (requires the 5 instance-coordination beans when true)
+ multi-instance-enabled: ${APP_MULTI_INSTANCE_ENABLED:false}
+ # true | false (whether to run Flyway migrations automatically at startup)
+ migration-on-startup: ${APP_MIGRATION_ON_STARTUP:true}
+ # feature-distributed-lock-contract D5 — distributed lock acquisition contract (try-lock +
+ # finite wait + lease TTL). Bound to LockSettings (adapter-persistence). Plain values, NOT
+ # APP_* env keys — new env-key registration is feature-env-driven-runtime-configuration's
+ # domain (out of scope for this branch); code defaults in LockSettings mirror these.
+ lock:
+ wait-time: 3s
+ lease-ttl: 30s
+ # Which RDBMS vendor composition the persistence adapter registers: postgresql | h2. Bound to
+ # PersistenceVendorSettings (adapter-persistence), which rejects any other value at startup.
+ #
+ # A property rather than a profile check, because the vendor belongs to the datastore and not to
+ # the environment that happens to use it. The profiles then state their choice:
+ # application-local.yml selects h2, application-dev.yml and application-prod.yml select
+ # postgresql, and prod additionally refuses h2 through PersistenceVendorProdSafetyValidator.
+ #
+ # The value here is the default for a deployment that activates no profile at all — PostgreSQL,
+ # the vendor every deployment ran before this selector existed.
+ # Notification delivery platform. Disabled by default, and disabled means nothing is bound: no
+ # provider runtime, no schema activation check, no scheduler thread, no secret required. Enabling
+ # it needs a real database with the notification schema stream applied and at least one provider
+ # profile with credentials — see docs/notification/configuration-reference.md, which is the
+ # reference this tree is kept in step with.
+ #
+ # Every value carries an inline default so that a deployment leaving the platform off never has to
+ # supply an environment variable for a subsystem it is not running. The tree existed only as
+ # relaxed-binding environment variables until 2026-08-15: a template user could not discover which
+ # profile, secret, callback and activation settings had to line up, because nothing wrote them
+ # down together.
+ presentation:
+ # feature-api-contract-baseline D2: API version prefix. Default is the URI
+ # prefix "/v1" (major-version path, AIP-185); override via env, or set "" for
+ # no prefix. The supplemental "X-Api-Version" header never overrides the path.
+ api-base-path: ${PRESENTATION_API_BASE_PATH:/v1}
+ async:
+ # feature-background-job-async-contract D7 — @Async ThreadPoolTaskExecutor pool sizing.
+ # Registry SSOT: docs/registries/env-keys.yaml (APP_ASYNC_EXECUTOR_* rows 1337-1377).
+ # Bound into AsyncExecutorSettings @ConfigurationProperties(prefix = "ca-skeleton.async.executor").
+ # Bounded queue is mandatory (D7) — an unbounded queue makes max-size unreachable.
+ executor:
+ # int >= 1 (positive_int). Always-alive worker count.
+ core-size: ${APP_ASYNC_EXECUTOR_CORE_SIZE:10}
+ # int >= core-size (positive_int_ge_core). Hard ceiling on workers.
+ max-size: ${APP_ASYNC_EXECUTOR_MAX_SIZE:50}
+ # int in 1..)
+ #
+ # Derived from the API base path, not restated. The default here was the literal
+ # /api/healthcheck while presentation.api-base-path defaults to /v1, so the two shipped defaults
+ # described one address and disagreed about it: the health endpoint was published at
+ # /v1/healthcheck and the allowlist opened /api/healthcheck, a path no handler serves. The
+ # public health endpoint was therefore not public in any runtime except local, which pins both
+ # to /api and so never saw it. Found by the shared-infra-dev lane, where the smoke client got a
+ # refusal from an application that had started perfectly.
+ public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck}
+ session:
+ cookie-name: ${APP_SESSION_COOKIE_NAME:CA_SESSION}
+ secure: ${APP_SESSION_COOKIE_SECURE:true}
+ http-only: ${APP_SESSION_COOKIE_HTTP_ONLY:true}
+ same-site: ${APP_SESSION_COOKIE_SAME_SITE:Lax}
+ path: ${APP_SESSION_COOKIE_PATH:/}
+ csrf-cookie-name: ${APP_SESSION_CSRF_COOKIE_NAME:XSRF-TOKEN}
+ csrf-header-name: ${APP_SESSION_CSRF_HEADER_NAME:X-XSRF-TOKEN}
+ authz:
+ # feature-authentication-authorization-contract D2/D3/D8: app-side role→permission
+ # mapping (the default source; IdP-issued permission claims are an out-of-scope
+ # alternative). Keys are RAW IdP role names (no ROLE_ prefix — that prefix only
+ # exists on Spring authorities, not on the principal's raw role set), looked up
+ # case-insensitively. Values are explicitly enumerated `resource:action` permissions
+ # (no wildcards — least-privilege, OWASP-AUTHZ-C4). The values below are the
+ # sample-portfolio demonstration (§5): `user` may read/write, only `admin` may close.
+ role-permissions:
+ user: worklog:read,worklog:write
+ admin: worklog:read,worklog:write,worklog:close
+ # The notification platform's own two permissions, granted to two roles rather than one.
+ # Publishing a template changes what every future submission renders, so a service that may
+ # send notifications is not thereby a service that may rewrite them; a deployment that wants
+ # both grants both roles.
+ notification-submit: notification:submit
+ notification-template-publish: notification-template:publish
+ cors:
+ # true | false
+ enabled: ${APP_SECURITY_CORS_ENABLED:false}
+ # comma-separated. Empty by default, unlike the other undefaulted values: CORS is off by
+ # default, and an empty origin list is the safest possible value rather than an arbitrary one.
+ # The rule those others follow — no default for an address, a credential, or an identity — is
+ # about not running against something nobody chose; allowing nobody is a choice.
+ allowed-origins: ${APP_SECURITY_CORS_ORIGINS:}
+ # comma-separated; empty -> defaults
+ allowed-methods: ${APP_SECURITY_CORS_ALLOWED_METHODS:}
+ # comma-separated; "*" allows any
+ allowed-headers: ${APP_SECURITY_CORS_ALLOWED_HEADERS:*}
+ # true | false
+ allow-credentials: ${APP_SECURITY_CORS_ALLOW_CREDENTIALS:false}
+ # seconds
+ max-age-seconds: ${APP_SECURITY_CORS_MAX_AGE:600}
diff --git a/src/app-bootstrap/src/main/resources/config/server.yml b/src/app-bootstrap/src/main/resources/config/server.yml
new file mode 100644
index 00000000..16014fdf
--- /dev/null
+++ b/src/app-bootstrap/src/main/resources/config/server.yml
@@ -0,0 +1,28 @@
+# 서버 포트·종료·프록시 헤더·압축.
+#
+# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다
+# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다.
+server:
+ # integer 1-65535
+ port: ${APP_SERVER_PORT:8080}
+ # graceful | immediate
+ shutdown: ${APP_SERVER_SHUTDOWN:graceful}
+ # none | native | framework (trust X-Forwarded-* behind LB/proxy)
+ forward-headers-strategy: ${APP_SERVER_FORWARD_HEADERS_STRATEGY:framework}
+ tomcat:
+ threads:
+ # integer >= 1
+ max: ${APP_SERVER_TOMCAT_MAX_THREADS:200}
+ # integer >= 0
+ min-spare: ${APP_SERVER_TOMCAT_MIN_SPARE_THREADS:10}
+ # integer >= 0 (OS-level backlog queue depth)
+ accept-count: ${APP_SERVER_TOMCAT_ACCEPT_COUNT:100}
+ # integer >= 1 (hard cap on simultaneously open connections)
+ max-connections: ${APP_SERVER_TOMCAT_MAX_CONNECTIONS:8192}
+ # duration: 20s | 1m
+ connection-timeout: ${APP_SERVER_TOMCAT_CONNECTION_TIMEOUT:20s}
+ compression:
+ # true | false
+ enabled: ${APP_SERVER_COMPRESSION_ENABLED:true}
+ # bytes or sized: 1024 | 1KB | 2KB
+ min-response-size: ${APP_SERVER_COMPRESSION_MIN_RESPONSE_SIZE:1KB}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java
index 55a50234..8304834a 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java
@@ -138,7 +138,7 @@ public final class AdapterActivationInventory {
new Ownership(
Set.of(
"dev.caskeleton.adapter.outbound.notification",
- "dev.caskeleton.bootstrap.notification"),
+ "dev.caskeleton.bootstrap.autoconfigure.notification"),
Set.of(),
"notification-"));
byAdapter.put(
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java
index 0a65e2bd..ec4d36c3 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java
@@ -2,6 +2,7 @@ package dev.caskeleton.bootstrap.activation;
import static org.assertj.core.api.Assertions.assertThat;
+import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration;
import dev.caskeleton.shared.activation.MasterSwitch;
import java.io.IOException;
import java.nio.file.Files;
@@ -27,7 +28,9 @@ class MasterSwitchRegistryContractTest {
@DisplayName("every switch is registered, defaults to false, and is bound by the shipped YAML")
void everySwitchAgreesAcrossItsThreeHomes(MasterSwitch masterSwitch) throws IOException {
String registry = read("docs/registries/env-keys.yaml");
- String applicationYaml = read("src/app-bootstrap/src/main/resources/application.yml");
+ // Every shipped resource, not application.yml alone: the capability blocks these
+ // switches bind moved to config/*.yml when the 974-line file was split (BOOT-008).
+ String applicationYaml = ShippedConfiguration.text();
assertThat(registry)
.as(
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java
index 2f8e6c7b..11a2279c 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java
@@ -2,7 +2,7 @@ package dev.caskeleton.bootstrap.activation;
import static org.assertj.core.api.Assertions.assertThat;
-import dev.caskeleton.bootstrap.notification.NotificationRootAutoConfiguration;
+import dev.caskeleton.bootstrap.autoconfigure.notification.NotificationRootAutoConfiguration;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
@@ -38,17 +38,17 @@ class NotificationRootImportCoverageTest {
+ "scan narrowed, a root that does not name them leaves the capability broken when "
+ "it is switched on, and quieter when it is off — which reads as success")
.contains(
- "dev.caskeleton.bootstrap.notification.NotificationPlatformRuntimeConfig",
- "dev.caskeleton.bootstrap.notification.NotificationPlatformSecretsConfig",
- "dev.caskeleton.bootstrap.notification.NotificationPlatformRegistriesConfig");
+ "dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformRuntimeConfig",
+ "dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformSecretsConfig",
+ "dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformRegistriesConfig");
}
@Test
@DisplayName("the delivery-platform runtime still names its own children")
void theRuntimeConfigNamesItsChildren() {
Import imports =
- dev.caskeleton.bootstrap.notification.NotificationPlatformRuntimeConfig.class.getAnnotation(
- Import.class);
+ dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformRuntimeConfig.class
+ .getAnnotation(Import.class);
assertThat(imports).isNotNull();
assertThat(Arrays.stream(imports.value()).map(Class::getSimpleName).toList())
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java
new file mode 100644
index 00000000..7949b51e
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java
@@ -0,0 +1,620 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
+import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaField;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The boundaries between adapters: inbound, outbound and persistence stay unaware of each other,
+ * and the composition root reaches each one only through its exported surface. Adapters are peers,
+ * not a layer — an edge between two of them is a dependency nobody declared.
+ *
+ *
어댑터끼리의 경계 — 인바운드/아웃바운드/영속성이 서로를 모르게 유지한다.
+ *
+ *
{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by
+ * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields
+ * inside a nested class — the rules would have stopped running while the build stayed green
+ * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share
+ * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest}
+ * holds the full rule inventory so none can be lost in a move like this one.
+ */
+@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class)
+class AdapterBoundaryArchitectureTest {
+
+ /**
+ * A transport must depend on an inbound port, never on the class behind it.
+ *
+ *
The notification callback controller injected {@code
+ * IngestProviderCallbackApplicationUseCase} directly. The existing capability rules did not catch
+ * it, and could not: they inspect types that already implement {@code CommandUseCase} or {@code
+ * QueryUseCase}, so an orchestration that never adopted the marker is invisible to the very gate
+ * that would have required it. This rule looks from the other side — at what the transport
+ * depends on — which is where the omission is visible.
+ */
+ @ArchTest
+ static final ArchRule INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.adapter.inbound..")
+ .should()
+ .dependOnClassesThat(
+ new com.tngtech.archunit.base.DescribedPredicate<>(
+ "are concrete application orchestration services") {
+ @Override
+ public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) {
+ return candidate.getPackageName().startsWith("dev.caskeleton.application.")
+ && !candidate.isInterface()
+ && !candidate
+ .getModifiers()
+ .contains(com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT)
+ && (candidate.getSimpleName().endsWith("Service")
+ || candidate.getSimpleName().endsWith("Orchestrator"));
+ }
+ })
+ .because(
+ "INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES: an entrypoint reached "
+ + "through a concrete class bypasses the use-case fitness gate, which is what "
+ + "declares transaction mode, repository access and outbound permission")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule WEB_ADAPTER_DOES_NOT_DEPEND_ON_PERSISTENCE_OR_OUTBOUND_ADAPTERS =
+ noClasses()
+ .that()
+ .resideInAPackage("..adapter.inbound.web..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "..adapter.outbound.persistence..",
+ "..adapter.outbound..",
+ "..adapter.outbound.identifier..",
+ "org.springframework.data.repository..",
+ "org.springframework.data.jpa.repository..",
+ "jakarta.persistence..",
+ "javax.persistence..",
+ "org.hibernate..");
+
+ @ArchTest
+ static final ArchRule PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS =
+ noClasses()
+ .that()
+ .resideInAPackage("..adapter.outbound.persistence..")
+ .should()
+ // persistence now nests under adapter.outbound (topology rename), so the forbidden
+ // outbound target must exclude persistence's own package — otherwise the abundant
+ // intra-persistence references (adapter -> entity/mapper/repository) self-overlap.
+ // Mirrors the IDENTIFIER_ADAPTER_... predicate-form self-exclusion below.
+ .dependOnClassesThat(
+ JavaClass.Predicates.resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..")
+ .or(
+ JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
+ .and(
+ JavaClass.Predicates.resideOutsideOfPackage(
+ "..adapter.outbound.persistence.."))))
+ .as(
+ "persistence-jpa is an internal (driven) outbound adapter — it must not reach into "
+ + "the web adapter, sibling outbound adapters, or the composition root "
+ + "(intra-persistence references are allowed).")
+ .allowEmptyShould(true);
+
+ // ---- persistence-rdbms/no-sql-extensibility (RDBMS base vendor-neutrality) ----
+ // adapter-persistence-rdbms (all of ..adapter.outbound.persistence.. EXCEPT
+ // ..adapter.outbound.persistence.postgresql..)
+ // must not import PostgreSQL driver or flyway-database-postgresql types. Those vendor-specific
+ // dependencies belong only in adapter-persistence-postgresql.
+ //
+ // 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
+ * called its roll-up to work out what a request's status should be — so "one recipient completed
+ * and one failed means partially completed", a product rule, lived behind a JPA store, and
+ * changing it meant editing the application and the database layer in step.
+ *
+ *
The test is concreteness, not naming. An application interface named {@code ...Service} —
+ * {@code FileQuotaService}, for instance — is a port: the adapter implements it, so the
+ * dependency points from infrastructure to a contract, which is the direction this architecture
+ * wants. A concrete application service is orchestration, and an adapter that calls one has taken
+ * over a decision the use case owns.
+ */
+ @ArchTest
+ static final ArchRule PERSISTENCE_DOES_NOT_DEPEND_ON_APPLICATION_SERVICES =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
+ .should()
+ .dependOnClassesThat(
+ new com.tngtech.archunit.base.DescribedPredicate<>(
+ "application services or use cases") {
+ @Override
+ public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) {
+ String name = candidate.getName();
+ return name.startsWith("dev.caskeleton.application.")
+ && !candidate.isInterface()
+ && (candidate.getSimpleName().endsWith("Service")
+ || candidate.getSimpleName().endsWith("UseCase"));
+ }
+ })
+ .as(
+ "adapter:outbound:persistence-jpa must not depend on application services or use "
+ + "cases: an adapter that calls one has taken over a decision the use case owns.")
+ .allowEmptyShould(true);
+
+ static final ArchRule PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
+ .and()
+ .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("org.postgresql..", "org.flywaydb.database.postgresql..")
+ .as(
+ "adapter:outbound:persistence-jpa (non-postgresql) must stay vendor-neutral: "
+ + "PostgreSQL driver / flyway-database-postgresql types live only in the "
+ + ".postgresql subpackage of adapter:outbound:persistence-jpa.")
+ .allowEmptyShould(true);
+
+ // The same fence around the second vendor. H2 arrives as a runtimeOnly driver, so today nothing
+ // imports org.h2.. and this rule holds vacuously — which is the point of writing it now: the
+ // first class that reaches for an H2 type has to do it inside the vendor package, before the
+ // "vendor-neutral base" claim quietly stops being true.
+ @ArchTest
+ static final ArchRule PERSISTENCE_RDBMS_STAYS_NEUTRAL_OF_H2 =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
+ .and()
+ .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.h2..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("org.h2..")
+ .as(
+ "adapter:outbound:persistence-jpa (non-h2) must stay vendor-neutral: H2 driver types "
+ + "live only in the .h2 subpackage of adapter:outbound:persistence-jpa.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS =
+ fields()
+ .that()
+ .areAnnotatedWith("jakarta.persistence.Column")
+ .and()
+ .areDeclaredInClassesThat()
+ .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
+ .and()
+ .areDeclaredInClassesThat()
+ .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..")
+ .should(notDeclareColumnDefinition())
+ .as(
+ "adapter:outbound:persistence-jpa (non-postgresql) must not pin vendor SQL "
+ + "columnDefinition strings; RDBMS physical types belong to vendor Flyway "
+ + "migrations, while rdbms entities may use portable JPA/Hibernate type hints.")
+ .allowEmptyShould(true);
+
+ // NOTE: adapter.outbound.identifier AND adapter.outbound.persistence are both nested under
+ // adapter.outbound (sibling adapters, not top-level packages), so the rules below exclude each
+ // one's own package from the side that would otherwise match it against itself — mirroring the
+ // resideOutsideOfPackage sibling-exclusion style already used for
+ // PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL / PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES
+ // above. Without this, an intra-identifier or intra-persistence reference (a class referencing a
+ // class in its own nested adapter) would spuriously violate one of the rules.
+
+ @ArchTest
+ static final ArchRule OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS =
+ noClasses()
+ .that()
+ .resideInAPackage("..adapter.outbound..")
+ .and()
+ .resideOutsideOfPackage("..adapter.outbound.identifier..")
+ // persistence-jpa is itself a nested outbound adapter; exclude its own package from the
+ // selector so its intra-references (which land in the ..adapter.outbound.persistence..
+ // forbidden target below) do not self-overlap. Generic outbound adapters (messaging,
+ // cache, http, ...) are still forbidden from depending on persistence.
+ .and()
+ .resideOutsideOfPackage("..adapter.outbound.persistence..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "..adapter.inbound.web..",
+ "..adapter.outbound.persistence..",
+ "..adapter.outbound.identifier..",
+ "..bootstrap..")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP =
+ noClasses()
+ .that()
+ .resideInAPackage("..adapter.outbound.identifier..")
+ .should()
+ .dependOnClassesThat(
+ JavaClass.Predicates.resideInAnyPackage(
+ "..adapter.inbound.web..",
+ "..adapter.outbound.persistence..",
+ "..bootstrap..",
+ "org.springframework.data.repository..",
+ "org.springframework.data.jpa.repository..",
+ "jakarta.persistence..",
+ "javax.persistence..",
+ "org.hibernate..")
+ .or(
+ JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
+ .and(
+ JavaClass.Predicates.resideOutsideOfPackage(
+ "..adapter.outbound.identifier.."))))
+ .as(
+ "adapter:outbound:identifier is a non-IO driven adapter (UUIDv7 generation/codec) —"
+ + " it must not reach into sibling adapters, persistence, or the composition root"
+ + " (feature-resource-identifier-contract §4 taxonomy).")
+ .allowEmptyShould(true);
+
+ // ---- Task 7: explicit inbound/outbound adapter topology (hexagonal driving/driven split) ----
+ // The moves in Tasks 1–6 established the new package topology; the three rules below make it an
+ // ENFORCED invariant rather than a convention. The inbound (driving) and outbound (driven)
+ // adapter families must never reach across to each other — every interaction between them is
+ // mediated by an application port — and the outbound concerns are peers that share code only
+ // through :adapter:outbound:support. These generalise the per-adapter isolation rules above
+ // (WEB_..., PERSISTENCE_..., OUTBOUND_..., IDENTIFIER_...) into the two family-level directions.
+
+ @ArchTest
+ static final ArchRule INBOUND_ADAPTERS_DO_NOT_DEPEND_ON_OUTBOUND_ADAPTERS =
+ noClasses()
+ .that()
+ .resideInAPackage("..adapter.inbound..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("..adapter.outbound..")
+ .as(
+ "inbound (driving) adapters must not depend on outbound (driven) adapters — an "
+ + "inbound adapter drives the application through its inbound ports and reaches "
+ + "persistence / messaging / cache / http only via application outbound ports, "
+ + "never by touching an outbound adapter directly (Task 7 adapter topology)");
+
+ @ArchTest
+ static final ArchRule OUTBOUND_ADAPTERS_DO_NOT_DEPEND_ON_INBOUND_ADAPTERS =
+ noClasses()
+ .that()
+ .resideInAPackage("..adapter.outbound..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("..adapter.inbound..")
+ .as(
+ "outbound (driven) adapters must not depend on inbound (driving) adapters — a driven "
+ + "adapter implements an application outbound port and is oblivious to which "
+ + "inbound adapter (web today, a future messaging-inbound tomorrow) triggered "
+ + "the call (Task 7 adapter topology)");
+
+ @ArchTest
+ static final ArchRule OUTBOUND_ADAPTERS_ARE_PEERS_SHARING_ONLY_SUPPORT =
+ slices()
+ .matching("..adapter.outbound.(*)..")
+ .namingSlices("$1")
+ .should()
+ .notDependOnEachOther()
+ // :adapter:outbound:support is the shared base (OutboundCorrelation, fail-open
+ // dependency logging, the @Configuration seam) that every outbound adapter is allowed
+ // to build on — a dependency whose TARGET resides in support is the sanctioned
+ // shared-code path, not peer-to-peer coupling, so it is ignored. Every other
+ // cross-slice edge (e.g. messaging -> cache, persistence -> httpclient) is forbidden.
+ .ignoreDependency(
+ DescribedPredicate.alwaysTrue(),
+ JavaClass.Predicates.resideInAPackage("..adapter.outbound.support.."))
+ .as(
+ "outbound adapters (persistence / identifier / cache / httpclient / messaging / "
+ + "notification) are peers: none may depend on a sibling; shared outbound code "
+ + "lives in :adapter:outbound:support (Task 7 adapter topology)");
+
+ @ArchTest
+ static final ArchRule CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES =
+ noClasses()
+ .that(CleanArchitectureRuleSupport.ARE_CONTROLLERS)
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "..repository..",
+ "..adapter.outbound.persistence..",
+ "..adapter.outbound.persistence..entity..",
+ "org.springframework.data.repository..",
+ "org.springframework.data.jpa.repository..",
+ "jakarta.persistence..",
+ "javax.persistence..");
+
+ /**
+ * A datastore handle is a capability, and only the adapter that owns it may hold one.
+ *
+ *
The MongoDB guardrail existed as a set of forbidden type names with a test that asserted the
+ * set's contents. Nothing compared it to a class, so "a controller must not hold a MongoTemplate"
+ * was a passing test about a {@code Set} while every controller was governed by nothing —
+ * and Boot's own auto-configuration supplies a {@code MongoTemplate}, a {@code MongoClient} and a
+ * {@code MongoDatabase} as beans, so the injection was one constructor parameter away.
+ *
+ *
Checked here, at the composition root, because this is the only place where every production
+ * class is on one classpath: a rule that runs inside the Mongo leaf can only see the leaf, and
+ * the types it exists to keep out live everywhere else.
+ *
+ *
A driver handle bypasses the whole platform at once — collection profile, consistency
+ * profile, timeout budget, failure translation, observation — which is why the ban covers the
+ * driver packages rather than a list of class names that a new driver release can extend.
+ */
+ @ArchTest
+ static final ArchRule ONLY_THE_PERSISTENCE_ADAPTER_HOLDS_A_MONGO_HANDLE =
+ noClasses()
+ .that()
+ .resideInAnyPackage(
+ "dev.caskeleton.domain..",
+ "dev.caskeleton.application..",
+ "dev.caskeleton.adapter.inbound..",
+ "dev.caskeleton.bootstrap..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("com.mongodb..", "org.springframework.data.mongodb..")
+ .as(
+ "only the MongoDB outbound adapter holds a MongoDB template, client, database or "
+ + "collection; every other layer reaches MongoDB through an application "
+ + "outbound port");
+
+ /**
+ * Which notification platform package may depend on which.
+ *
+ *
The design maps 31 logical modules onto packages inside five Gradle leaves, and the registry
+ * gate stops at the leaf boundary — so inside a leaf any package could reach any other and the
+ * layering existed only in the document. This table is the layering, and it is derived from the
+ * edges the code actually has: adding one means adding it here, in review, rather than
+ * discovering later that the module graph in the design and the import graph in the source
+ * disagree.
+ *
+ *
{@code api} depends on nothing, which is what makes it usable as the contract everything
+ * else shares.
+ */
+ private static final Map> NOTIFICATION_PLATFORM_PACKAGE_EDGES =
+ Map.ofEntries(
+ Map.entry("api", Set.of()),
+ // port -> template: the template publication port speaks the platform's own template
+ // vocabulary — a slot, a published version — rather than restating it as strings. The
+ // edge
+ // is one-way; template depends on api alone, so nothing cycles through it.
+ Map.entry("port", Set.of("api", "template")),
+ // admin -> callback/dispatch/observation: the operator plane moved here from the outbound
+ // adapter, where operator authorization and transaction orchestration did not belong.
+ Map.entry(
+ "admin", Set.of("api", "callback", "dispatch", "observation", "policy", "provider")),
+ // callback -> port is an implementation depending on the interface it implements, which
+ // is the direction that makes the port a boundary. The reverse edge is what was removed.
+ Map.entry("callback", Set.of("api", "observation", "port")),
+ // dispatch -> port: the split inbound use cases live beside the service they wrap and
+ // implement the port interfaces callers depend on.
+ Map.entry(
+ "dispatch",
+ Set.of(
+ "api",
+ "port",
+ "callback",
+ "contact",
+ "observation",
+ "policy",
+ "provider",
+ "push",
+ "security",
+ "template")),
+ Map.entry("contact", Set.of("api")),
+ Map.entry("email", Set.of("api")),
+ Map.entry("inbox", Set.of("api")),
+ Map.entry("observation", Set.of("api")),
+ Map.entry("policy", Set.of("api", "contact", "provider")),
+ Map.entry("provider", Set.of("api", "callback", "security", "template")),
+ Map.entry("push", Set.of("api")),
+ Map.entry("security", Set.of("api", "contact")),
+ Map.entry("sms", Set.of("api", "contact")),
+ Map.entry("template", Set.of("api")),
+ Map.entry("webpush", Set.of("api")));
+
+ private static final String NOTIFICATION_PLATFORM_ROOT =
+ "dev.caskeleton.application.notification.platform";
+
+ @ArchTest
+ static final ArchRule NOTIFICATION_PLATFORM_PACKAGE_EDGES_ARE_REGISTERED =
+ classes()
+ .that()
+ .resideInAPackage(NOTIFICATION_PLATFORM_ROOT + "..")
+ .should(dependOnlyOnRegisteredNotificationPackages())
+ .as(
+ "a notification platform package may depend only on the packages registered for it; "
+ + "the leaf-level registry cannot see inside a Gradle project, so the module "
+ + "graph is enforced here or nowhere (NTF-022)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NOTIFICATION_PLATFORM_PACKAGES_ARE_ACYCLIC =
+ slices()
+ .matching(NOTIFICATION_PLATFORM_ROOT + ".(*)..")
+ .namingSlices("$1")
+ .should()
+ .beFreeOfCycles()
+ .as(
+ "a cycle between two notification packages means neither can be understood, "
+ + "tested, or extracted without the other (NTF-022)");
+
+ @ArchTest
+ static final ArchRule NOTIFICATION_ADAPTER_PACKAGES_ARE_ACYCLIC =
+ slices()
+ .matching("dev.caskeleton.adapter.outbound.notification.platform.(*)..")
+ .namingSlices("$1")
+ .should()
+ .beFreeOfCycles()
+ .as("the outbound notification adapter's packages form a DAG too (NTF-022)");
+
+ /**
+ * Hold every notification platform package to its registered edge set.
+ *
+ * @return a condition that reports each unregistered package edge
+ */
+ private static ArchCondition dependOnlyOnRegisteredNotificationPackages() {
+ return new ArchCondition<>("depend only on registered notification platform packages") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ String source = notificationPackageOf(item.getPackageName());
+ if (source == null) {
+ return;
+ }
+ Set allowed = NOTIFICATION_PLATFORM_PACKAGE_EDGES.get(source);
+ if (allowed == null) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "notification platform package '"
+ + source
+ + "' is not registered in NOTIFICATION_PLATFORM_PACKAGE_EDGES: "
+ + item.getName()));
+ return;
+ }
+ for (JavaClass target :
+ item.getDirectDependenciesFromSelf().stream()
+ .map(dependency -> dependency.getTargetClass())
+ .toList()) {
+ String edge = notificationPackageOf(target.getPackageName());
+ if (edge == null || edge.equals(source) || allowed.contains(edge)) {
+ continue;
+ }
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "unregistered notification platform package edge "
+ + source
+ + " -> "
+ + edge
+ + " ("
+ + item.getName()
+ + " -> "
+ + target.getName()
+ + ")"));
+ }
+ }
+ };
+ }
+
+ /**
+ * The notification platform sub-package a class belongs to.
+ *
+ * @param packageName the class's package
+ * @return the first segment below the platform root, or null when the class is outside it
+ */
+ private static String notificationPackageOf(String packageName) {
+ if (!packageName.startsWith(NOTIFICATION_PLATFORM_ROOT + ".")) {
+ return null;
+ }
+ String remainder = packageName.substring(NOTIFICATION_PLATFORM_ROOT.length() + 1);
+ int dot = remainder.indexOf('.');
+ return dot < 0 ? remainder : remainder.substring(0, dot);
+ }
+
+ private static ArchCondition notDeclareColumnDefinition() {
+ return new ArchCondition<>("not declare @Column columnDefinition") {
+ @Override
+ public void check(JavaField field, ConditionEvents events) {
+ field
+ .tryGetAnnotationOfType("jakarta.persistence.Column")
+ .ifPresent(
+ column -> {
+ String columnDefinition =
+ column.get("columnDefinition").map(Object::toString).orElse("");
+ if (!columnDefinition.isBlank()) {
+ events.add(
+ SimpleConditionEvent.violated(
+ field,
+ "Field "
+ + field.getFullName()
+ + " pins vendor SQL columnDefinition='"
+ + columnDefinition
+ + "' in adapter:outbound:persistence-jpa; move the "
+ + "physical type to the vendor migration."));
+ }
+ });
+ }
+ };
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java
new file mode 100644
index 00000000..a0008fac
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java
@@ -0,0 +1,841 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaClasses;
+import com.tngtech.archunit.core.domain.JavaEnumConstant;
+import com.tngtech.archunit.core.domain.JavaMethod;
+import com.tngtech.archunit.core.domain.JavaMethodCall;
+import com.tngtech.archunit.core.domain.JavaModifier;
+import com.tngtech.archunit.core.importer.ClassFileImporter;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import dev.caskeleton.application.capability.UseCaseCapability;
+import dev.caskeleton.application.security.RequiresPermission;
+import dev.caskeleton.application.usecase.CommandUseCase;
+import dev.caskeleton.application.usecase.QueryUseCase;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.springframework.boot.health.actuate.endpoint.HealthEndpoint;
+import org.springframework.boot.health.contributor.Health;
+import org.springframework.boot.health.contributor.HealthIndicator;
+
+/**
+ * What {@code application-core} may depend on, and how a use case declares the capability it needs.
+ * The two questions live together because the answer to the second is what makes the first
+ * enforceable: a use case that names its capability can be checked against the ports it actually
+ * touches.
+ *
+ *
application-core 가 무엇에 의존해도 되는가, 그리고 유스케이스가 자기 능력을 어떻게 선언하는가.
+ *
+ *
{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by
+ * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields
+ * inside a nested class — the rules would have stopped running while the build stayed green
+ * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share
+ * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest}
+ * holds the full rule inventory so none can be lost in a move like this one.
+ */
+@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class)
+class ApplicationBoundaryArchitectureTest {
+
+ @ArchTest
+ static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT =
+ noClasses()
+ .that()
+ .resideInAPackage("..application..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "..adapter..",
+ "..bootstrap..",
+ "..infra..",
+ "..presentation..",
+ "..cmd..",
+ "jakarta.persistence..",
+ "javax.persistence..",
+ "jakarta.servlet..",
+ "org.springframework.web..",
+ "org.hibernate..")
+ .allowEmptyShould(true);
+
+ /**
+ * The application never names the messaging platform's types.
+ *
+ *
The new platform has its own {@code MessagePublisher}, {@code MessageEnvelope} and {@code
+ * OutboxStatus}, and the application already has a canonical publish port and outbox model of its
+ * own. An import from application code would make the platform's vocabulary the business boundary
+ * — and its {@code OutboxStatus.FAILED} means the opposite of the legacy {@code
+ * OutboxEventStatus.FAILED}, so the two models cannot be mixed by name without inverting
+ * retryable and terminal. The translation belongs in an outbound bridge adapter.
+ */
+ @ArchTest
+ static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM =
+ noClasses()
+ .that()
+ .resideInAPackage("..application..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("dev.caskeleton.messaging..")
+ .because(
+ "APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM: the application owns its "
+ + "publish port and outbox model; a bridge adapter translates, and the two "
+ + "outbox status models mean opposite things under the same names")
+ .allowEmptyShould(true);
+
+ /**
+ * The two notification models must not learn about each other.
+ *
+ *
NOTIF-ADR-005 makes the platform canonical and keeps R1's hundred public types in place
+ * without new consumers. Nothing enforced that: {@code Channel}, plan, dispatch and
+ * receipt/evidence exist in both namespaces with the same simple names, so a new consumer picks
+ * one by import order and the two models drift apart while both claim to be current.
+ *
+ *
The exception is a single compatibility package. If a bridge is ever needed it goes there,
+ * and only there — an exception in several places is not an exception, it is the second canonical
+ * model the ADR exists to prevent.
+ */
+ @ArchTest
+ static final ArchRule NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.application.notification.platform..")
+ .and()
+ .resideOutsideOfPackage("dev.caskeleton.application.notification.compatibility.r1..")
+ .should()
+ .dependOnClassesThat(
+ new com.tngtech.archunit.base.DescribedPredicate<>(
+ "reside in the R1 notification namespace") {
+ @Override
+ public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) {
+ String name = candidate.getPackageName();
+ return name.equals("dev.caskeleton.application.notification");
+ }
+ })
+ .because(
+ "NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER: NOTIF-ADR-005 makes the "
+ + "platform canonical; a bridge belongs in "
+ + "dev.caskeleton.application.notification.compatibility.r1 and nowhere else")
+ .allowEmptyShould(true);
+
+ /**
+ * An orchestration entrypoint, recognised by what it does rather than by what it implements.
+ *
+ *
This is the gap NTF-019 names. Every existing capability rule starts from {@code
+ * .that().implement(CommandUseCase.class)} — so a new orchestration that simply never implements
+ * the marker is not checked by any of them. The rules were not wrong; they were unreachable. A
+ * class that opens a transaction and writes through a repository port is an orchestration
+ * entrypoint whether or not anybody remembered to say so.
+ */
+ private static final DescribedPredicate ARE_ORCHESTRATION_ENTRYPOINTS =
+ new DescribedPredicate<>("application orchestration entrypoints") {
+ @Override
+ public boolean test(JavaClass candidate) {
+ // ".application." rather than a prefix match, matching the glob convention the rest of
+ // this file uses — and, concretely, so the negative fixture that proves this rule fires
+ // is itself inside the rule's scope. A rule whose only corpus is production code cannot
+ // be shown to fire at all.
+ if (!candidate.getPackageName().contains(".application.")
+ || candidate.isInterface()
+ || candidate.getModifiers().contains(JavaModifier.ABSTRACT)
+ || candidate.isRecord()
+ || candidate.isEnum()) {
+ return false;
+ }
+ // Holding a transaction port is the tell. A helper that only computes has no boundary
+ // to declare; a class that can start a unit of work is deciding its shape.
+ //
+ // Read from the field types rather than from the dependency graph: an isolated corpus
+ // (the negative fixture) resolves the port as a stub, and the dependency edge is then
+ // absent — which would make this rule pass on the very shape it exists to catch.
+ return candidate.getFields().stream()
+ .anyMatch(
+ field ->
+ field
+ .getRawType()
+ .getFullName()
+ .equals("dev.caskeleton.application.transaction.TransactionPort"));
+ }
+ };
+
+ @ArchTest
+ static final ArchRule ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY =
+ classes()
+ .that(ARE_ORCHESTRATION_ENTRYPOINTS)
+ .and()
+ .haveSimpleNameEndingWith("UseCase")
+ .should()
+ .beAnnotatedWith(UseCaseCapability.class)
+ .as(
+ "NTF-019: a use case that opens a transaction declares its capability. The existing "
+ + "rules all start from the marker interface, so an entrypoint that never "
+ + "implements it is checked by nothing at all")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule OUTBOUND_NOTIFICATION_HOLDS_NO_ADMIN_POLICY =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.adapter.outbound.notification..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName("dev.caskeleton.application.transaction.TransactionPort")
+ .orShould()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName(
+ "dev.caskeleton.application.notification.platform.admin.AdminActor")
+ .orShould()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName(
+ "dev.caskeleton.application.notification.platform.admin.NotificationAdminAuthority")
+ .as(
+ "NTF-020: who may perform an operator action, and where a unit of work begins, are "
+ + "decisions about the product. They were both made in the layer whose job is "
+ + "speaking provider protocols, where no application test could reach them")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_A_CAPABILITY =
+ classes()
+ .that(
+ new DescribedPredicate("implement an inbound port") {
+ @Override
+ public boolean test(JavaClass candidate) {
+ return !candidate.isInterface()
+ && !candidate.getModifiers().contains(JavaModifier.ABSTRACT)
+ && candidate.getAllRawInterfaces().stream()
+ .anyMatch(
+ implemented -> implemented.getPackageName().contains(".port.in"));
+ }
+ })
+ .should()
+ .beAnnotatedWith(UseCaseCapability.class)
+ .as(
+ "NTF-019: implementing an inbound port is what makes a class an entrypoint, whatever "
+ + "it is named. The name-based half of this check would miss a class called "
+ + "NotificationSubmissionService that a controller reaches directly")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule USE_CASE_PORTS_CARRY_ONE_OPERATION =
+ classes()
+ .that()
+ .resideInAPackage("..application..port.in..")
+ .and()
+ .areInterfaces()
+ .should(declareAtMostOneAbstractOperation())
+ .as(
+ "NTF-019: one operation per inbound port. NotificationOrchestrator mixed submit, "
+ + "schedule, cancel and get, and no single transaction mode is true of all four "
+ + "— the compiler agrees, since CommandUseCase cannot be inherited twice with "
+ + "different type arguments")
+ .allowEmptyShould(true);
+
+ /**
+ * Hold an inbound port to a single abstract operation.
+ *
+ * @return the condition
+ */
+ private static ArchCondition declareAtMostOneAbstractOperation() {
+ return new ArchCondition<>("declare at most one abstract operation") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ List abstractMethods =
+ item.getMethods().stream()
+ .filter(method -> !method.getModifiers().contains(JavaModifier.STATIC))
+ .filter(method -> method.getModifiers().contains(JavaModifier.ABSTRACT))
+ .toList();
+ if (abstractMethods.size() > 1) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ item.getName()
+ + " declares "
+ + abstractMethods.size()
+ + " operations; an inbound port declares one, so its capability can be "
+ + "stated honestly"));
+ }
+ }
+ };
+ }
+
+ @ArchTest
+ static final ArchRule APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.application..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "org.slf4j..",
+ "java.util.logging..",
+ "ch.qos.logback..",
+ "org.apache.logging.log4j..",
+ "io.micrometer..")
+ .as(
+ "APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK: application-core owns typed outbound "
+ + "diagnostic ports, while logging and metrics implementations belong to "
+ + "outbound adapters")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule SAMPLE_APPLICATION_HAS_NO_SLF4J =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.sample.portfolio.application..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("org.slf4j..")
+ .as(
+ "SAMPLE_APPLICATION_HAS_NO_SLF4J: sample application collaborators read "
+ + "correlation context through application-core CorrelationIdPort; inbound "
+ + "adapters own MDC")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.sample.portfolio.application..")
+ .and()
+ .doNotHaveFullyQualifiedName(
+ "dev.caskeleton.sample.portfolio.application.poster.UploadPosterImageUseCase")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("dev.caskeleton.application.storage")
+ .as(
+ "Only the characterized UploadPosterImageUseCase may temporarily import the legacy "
+ + "ObjectStoragePort/StoredObject contract. New sample business code must use "
+ + "the semantic objectstorage ports. Task 23 moves compatibility into the exact "
+ + "legacy slice; Task 24 separately reviews the administrative migration seam.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION =
+ noClasses()
+ .that()
+ .resideInAPackage("..application..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional")
+ .as(
+ "application package must use TransactionPort instead of "
+ + "@Transactional (feature-application-port-usecase-contract D3)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE =
+ classes()
+ .that()
+ .implement(CommandUseCase.class)
+ .or()
+ .implement(QueryUseCase.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should()
+ .haveSimpleNameEndingWith("UseCase")
+ .as(
+ "CommandUseCase / QueryUseCase implementations must end with 'UseCase' "
+ + "(feature-application-port-usecase-contract D1)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY =
+ classes()
+ .that()
+ .implement(CommandUseCase.class)
+ .or()
+ .implement(QueryUseCase.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should()
+ .beAnnotatedWith(UseCaseCapability.class)
+ .as(
+ "every CommandUseCase / QueryUseCase implementation must declare "
+ + "@UseCaseCapability (feature-application-port-usecase-contract §판정 기준)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT =
+ noClasses()
+ .that()
+ .resideInAPackage("..application..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName("org.springframework.context.ApplicationContext")
+ .as(
+ "application package must not depend on Spring ApplicationContext "
+ + "(blocks getBean(Class) reflection-style bypass — "
+ + "feature-architecture-enforcement-rules D11). string-key bean "
+ + "lookup / Class.forName(String) bypass remains ArchUnit's "
+ + "static-analysis limit per D12.")
+ .allowEmptyShould(true);
+
+ // NOTE: the former `inbound_port_implementations_do_not_declare_keyed_idempotency`
+ // freeze rule (feature-application-port-usecase-contract D14) is removed now that
+ // feature-rate-limit-idempotency-contract has merged: Idempotency.KEYED is backed
+ // by IdempotencyExecutor + IdempotencyStorePort + the idempotency_record table, so a
+ // use case may declare it.
+
+ // ---- feature-repository-access-permission-contract D12 / D6 (repositoryAccess coherence) ----
+
+ @ArchTest
+ static final ArchRule READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS =
+ classes()
+ .that()
+ .areAnnotatedWith(UseCaseCapability.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should(notCallRepositoryWriteUnlessWriteRepository())
+ .as(
+ "D12 READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS: a use case "
+ + "whose @UseCaseCapability(repositoryAccess) is not WRITE_REPOSITORY must "
+ + "not DIRECTLY call a write method "
+ + "(save / saveAll / saveAndFlush / delete* / remove* / update / insert / "
+ + "persist / merge / store) of a *Repository port. Declaring READ_REPOSITORY "
+ + "(or NONE) then mutating is the silent read-to-write upgrade this contract "
+ + "blocks (feature-repository-access-permission-contract D12). "
+ + "UNSUPPORTED_IMPL_DECISION: ArchUnit static analysis only reaches DIRECT "
+ + "calls — a write reached through a helper / mapper escapes the call graph "
+ + "and stays a code-review concern (D12 static-analysis limit). The write "
+ + "method-name allowlist is a ca-tmpl convention; extend it when a new "
+ + "mutating verb enters the repository vocabulary.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS =
+ classes()
+ .that()
+ .areAnnotatedWith(UseCaseCapability.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should(haveWriteRepositoryAccessWhenBulkWrite())
+ .as(
+ "D6 BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS: "
+ + "@UseCaseCapability(bulkWrite = true) requires repositoryAccess = "
+ + "WRITE_REPOSITORY — a bulk write (N > 100) is still a write, and the "
+ + "capability registry binds BULK_WRITE to WRITE_REPOSITORY "
+ + "(feature-repository-access-permission-contract D6).")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY =
+ classes()
+ .that()
+ .areAnnotatedWith(UseCaseCapability.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should(callTransactionPortMethodRequiredByCapability())
+ .as(
+ "feature-domain-feature-onboarding-contract D4: a use case that declares a "
+ + "repository-backed transaction capability must call the matching "
+ + "TransactionPort boundary directly: READ_REPOSITORY+READ_ONLY -> inRead, "
+ + "WRITE_REPOSITORY+WRITE -> inWrite or inRootWrite, REQUIRES_NEW -> inNew. "
+ + "RepositoryAccess.NONE may intentionally skip a DB transaction. "
+ + "UNSUPPORTED_IMPL_DECISION: static analysis reaches direct calls only; a "
+ + "transaction hidden behind a helper remains a code-review concern.")
+ .allowEmptyShould(true);
+
+ // ---- feature-authentication-authorization-contract D1 / D4 (product authorization) ----
+
+ @ArchTest
+ static final ArchRule MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION =
+ classes()
+ .that()
+ .areAnnotatedWith(UseCaseCapability.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should(declareRequiredPermissionWhenMutating())
+ .as(
+ "D4 MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION: a use case whose "
+ + "@UseCaseCapability(repositoryAccess) is WRITE_REPOSITORY MUST also declare "
+ + "@RequiresPermission, so a mutating operation cannot silently ship without a "
+ + "permission gate (feature-authentication-authorization-contract D4). The "
+ + "skeleton baseline is mutating-only; public reads are exempt. "
+ + "UNSUPPORTED_IMPL_DECISION: this rule checks the type-level annotation (the "
+ + "@UseCaseCapability convention); a method-only @RequiresPermission on a use "
+ + "case still trips it. AOP self-invocation / non-bean entry points stay a "
+ + "static-analysis blind spot (D4 limit).")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY =
+ noClasses()
+ .that()
+ .resideInAnyPackage("..application..", "..domain..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("org.springframework.security..")
+ .as(
+ "D1 APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY: application-core and "
+ + "domain-core must stay free of Spring Security types. The authorization "
+ + "DECISION is a framework-free port (@RequiresPermission / AuthorizationPort / "
+ + "AuthorizationPrincipal); the Spring-aware enforcement MECHANISM "
+ + "(AuthorizationManager) lives only in adapter:inbound:web "
+ + "(feature-authentication-authorization-contract D1).")
+ .allowEmptyShould(true);
+
+ // ---- feature-management-actuator-security-contract D6 (health shape-ownership guardrail) ----
+ // ManagementSecurityConfig (in ..management.security..) must NOT import or depend on
+ // HealthEndpoint, HealthIndicator, or Health — the health endpoint shape belongs to
+ // the runtime-health branch. The security config only
+ // CleanArchitectureRuleSupport.matches/permits endpoints by id string;
+ // it never inspects health internals.
+
+ @ArchTest
+ static final ArchRule MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS =
+ noClasses()
+ .that()
+ .resideInAPackage("..management.security..")
+ .should()
+ .dependOnClassesThat()
+ .areAssignableTo(HealthEndpoint.class)
+ .orShould()
+ .dependOnClassesThat()
+ .areAssignableTo(HealthIndicator.class)
+ .orShould()
+ .dependOnClassesThat()
+ .areAssignableTo(Health.class)
+ .as(
+ "D6 MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS: classes in "
+ + "..management.security.. must not import or depend on HealthEndpoint, "
+ + "HealthIndicator, or Health — the health endpoint shape is owned "
+ + "by the runtime-health branch. The actuator security config CleanArchitectureRuleSupport.matches "
+ + "endpoints by id only, never by health internals "
+ + "(feature-management-actuator-security-contract D6).")
+ .allowEmptyShould(true);
+
+ private static final Set REPOSITORY_WRITE_METHOD_NAMES =
+ Set.of(
+ "save",
+ "saveAll",
+ "saveAndFlush",
+ "delete",
+ "deleteById",
+ "deleteAll",
+ "remove",
+ "removeAll",
+ "removeById",
+ "update",
+ "insert",
+ "persist",
+ "merge",
+ "store");
+
+ private static ArchCondition notCallRepositoryWriteUnlessWriteRepository() {
+ return new ArchCondition<>(
+ "not call a *Repository write method unless " + "repositoryAccess = WRITE_REPOSITORY") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
+ Object repositoryAccess = annotation.get("repositoryAccess").orElse(null);
+ boolean writeRepository =
+ repositoryAccess instanceof JavaEnumConstant constant
+ && "WRITE_REPOSITORY".equals(constant.name());
+ if (writeRepository) {
+ return; // WRITE_REPOSITORY use cases are permitted to mutate.
+ }
+ for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
+ String targetOwner = call.getTargetOwner().getSimpleName();
+ String targetName = call.getName();
+ if (targetOwner.endsWith("Repository")
+ && REPOSITORY_WRITE_METHOD_NAMES.contains(targetName)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " declares repositoryAccess != "
+ + "WRITE_REPOSITORY but directly calls "
+ + targetOwner
+ + "."
+ + targetName
+ + "(...) — a repository write "
+ + "(feature-repository-access-permission-contract D12)"));
+ }
+ }
+ }
+ };
+ }
+
+ private static ArchCondition declareRequiredPermissionWhenMutating() {
+ return new ArchCondition<>(
+ "declare @RequiresPermission when repositoryAccess = WRITE_REPOSITORY") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
+ Object repositoryAccess = annotation.get("repositoryAccess").orElse(null);
+ boolean writeRepository =
+ repositoryAccess instanceof JavaEnumConstant constant
+ && "WRITE_REPOSITORY".equals(constant.name());
+ if (!writeRepository) {
+ return;
+ }
+ if (!item.isAnnotatedWith(RequiresPermission.class.getName())) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " declares @UseCaseCapability(repositoryAccess = "
+ + "WRITE_REPOSITORY) but is missing @RequiresPermission — a mutating use "
+ + "case must declare the permission it requires "
+ + "(feature-authentication-authorization-contract D4)"));
+ }
+ }
+ };
+ }
+
+ private static ArchCondition haveWriteRepositoryAccessWhenBulkWrite() {
+ return new ArchCondition<>(
+ "declare repositoryAccess = WRITE_REPOSITORY when bulkWrite = true") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
+ boolean bulkWrite = Boolean.TRUE.equals(annotation.get("bulkWrite").orElse(false));
+ if (!bulkWrite) {
+ return;
+ }
+ Object repositoryAccess = annotation.get("repositoryAccess").orElse(null);
+ boolean writeRepository =
+ repositoryAccess instanceof JavaEnumConstant constant
+ && "WRITE_REPOSITORY".equals(constant.name());
+ if (!writeRepository) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " declares @UseCaseCapability(bulkWrite = true) "
+ + "without repositoryAccess = WRITE_REPOSITORY "
+ + "(feature-repository-access-permission-contract D6)"));
+ }
+ }
+ };
+ }
+
+ @SuppressWarnings("resource")
+ private static ArchCondition callTransactionPortMethodRequiredByCapability() {
+ return new ArchCondition<>("call the TransactionPort method required by @UseCaseCapability") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
+ String transactionMode = enumAnnotationValue(annotation, "transactionMode");
+ String repositoryAccess = enumAnnotationValue(annotation, "repositoryAccess");
+
+ Set requiredMethods = Set.of();
+ if ("REQUIRES_NEW".equals(transactionMode)) {
+ requiredMethods = Set.of("inNew");
+ } else if ("WRITE".equals(transactionMode) && "WRITE_REPOSITORY".equals(repositoryAccess)) {
+ requiredMethods = Set.of("inWrite", "inRootWrite");
+ } else if ("READ_ONLY".equals(transactionMode)
+ && "READ_REPOSITORY".equals(repositoryAccess)) {
+ requiredMethods = Set.of("inRead");
+ }
+
+ if (requiredMethods.isEmpty()) {
+ return;
+ }
+
+ for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
+ if ("dev.caskeleton.application.transaction.TransactionPort"
+ .equals(call.getTargetOwner().getFullName())
+ && requiredMethods.contains(call.getName())) {
+ return;
+ }
+ }
+
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " declares @UseCaseCapability(transactionMode = "
+ + transactionMode
+ + ", repositoryAccess = "
+ + repositoryAccess
+ + ") but does not directly call one of TransactionPort."
+ + requiredMethods
+ + "(...)"));
+ }
+ };
+ }
+
+ private static String enumAnnotationValue(JavaAnnotation> annotation, String property) {
+ Object value = annotation.get(property).orElse(null);
+ return value instanceof JavaEnumConstant constant ? constant.name() : "";
+ }
+
+ // ---- feature-repository-access-permission-contract D7 (outbound capability coherence) ----
+
+ /**
+ * Outbound ports are the interfaces a use case must hold {@code externalOutboundAllowed} to call.
+ * They are identified by their adapter binding — any interface implemented by a production class
+ * residing under {@code ..adapter.outbound..} (e.g. RepoStatsPort ← RepoStatsPortClient),
+ * EXCLUDING the {@code ..adapter.outbound.identifier..} sub-package: UUIDv7 id/event-id factories
+ * are a non-IO driven adapter (in-process generation/codec, no external HTTP / message / cache
+ * call — see IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP), so calling one
+ * directly does not require {@code externalOutboundAllowed = true}. Also EXCLUDING the {@code
+ * ..adapter.outbound.persistence..} sub-package: repositories/stores are internal (driven)
+ * outbound adapters — a use case calling a repository does not require {@code
+ * externalOutboundAllowed = true}, and their package moved under {@code adapter.outbound} only as
+ * a topology rename (Task 7 owns the holistic external-vs-internal split). Precomputed from
+ * production classes (tests excluded) so the coherence rule has no application-side marker
+ * dependency.
+ */
+ private static final Set OUTBOUND_PORT_NAMES = outboundPortNames();
+
+ private static Set outboundPortNames() {
+ JavaClasses production =
+ new ClassFileImporter()
+ .withImportOption(new ProductionClassImportOption())
+ .importPackages("dev.caskeleton");
+ Set names = new HashSet<>();
+ for (JavaClass type : production) {
+ if (type.getPackageName().contains(".adapter.outbound.")
+ && !isInProcessDrivenOutbound(type.getPackageName())) {
+ for (JavaClass iface : type.getAllRawInterfaces()) {
+ names.add(iface.getFullName());
+ }
+ }
+ }
+ return names;
+ }
+
+ /**
+ * True when {@code packageName} is an IN-PROCESS (non-IO) driven outbound concern — {@code
+ * ..adapter.outbound.persistence..} (repositories / stores) or {@code
+ * ..adapter.outbound.identifier..} (UUIDv7 id/event-id generation & codec). Both nest under
+ * {@code ..adapter.outbound..} as a Task-1–6 topology rename but perform NO external IO (no HTTP
+ * / message / cache network call), so an interface they implement is NOT an external outbound
+ * port and a use case calling one directly does not require {@code externalOutboundAllowed =
+ * true}. This single predicate consolidates the two per-package {@code .contains(...)} exclusions
+ * that Tasks 2/6 added locally to {@link #outboundPortNames()}.
+ *
+ *
The genuinely external-IO outbound adapters — {@code httpclient}, {@code messaging}, {@code
+ * notification}, and {@code cache} — are deliberately NOT treated as in-process here: a Redis
+ * cache call is a real network dependency, so the D7 external-outbound gate must keep covering
+ * it. Folding cache/support into this exclusion (as an earlier plan sketch suggested) would
+ * weaken that gate for no current benefit and contradict this heuristic's own IO-vs-in- process
+ * rationale, so it is intentionally NOT done.
+ */
+ private static boolean isInProcessDrivenOutbound(String packageName) {
+ return packageName.contains(".adapter.outbound.persistence")
+ || packageName.contains(".adapter.outbound.identifier");
+ }
+
+ @ArchTest
+ static final ArchRule EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY =
+ classes()
+ .that()
+ .areAnnotatedWith(UseCaseCapability.class)
+ .and()
+ .areNotInterfaces()
+ .and()
+ .areNotAnonymousClasses()
+ .and()
+ .areNotMemberClasses()
+ .should(notCallOutboundPortUnlessAllowed())
+ .as(
+ "D7 EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY: a "
+ + "use case that DIRECTLY calls an outbound port (an interface implemented "
+ + "by a production class under ..adapter.outbound..) must declare "
+ + "@UseCaseCapability(externalOutboundAllowed = true). Without it, emitting "
+ + "an external HTTP / message / cache call is forbidden "
+ + "(feature-repository-access-permission-contract D7). Outbound ports are "
+ + "identified by their adapter binding (RepoStatsPort ← RepoStatsPortClient), "
+ + "so no application-side marker is required. UNSUPPORTED_IMPL_DECISION: "
+ + "ArchUnit reaches DIRECT calls only — a call routed through a helper / "
+ + "mapper escapes the call graph and stays a code-review concern "
+ + "(static-analysis limit, mirrors D12).")
+ .allowEmptyShould(true);
+
+ private static ArchCondition notCallOutboundPortUnlessAllowed() {
+ return new ArchCondition<>("not call an outbound port unless externalOutboundAllowed = true") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
+ boolean allowed =
+ Boolean.TRUE.equals(annotation.get("externalOutboundAllowed").orElse(false));
+ if (allowed) {
+ return; // externalOutboundAllowed use cases may emit outbound calls.
+ }
+ for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
+ String targetOwner = call.getTargetOwner().getFullName();
+ if (OUTBOUND_PORT_NAMES.contains(targetOwner)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " declares externalOutboundAllowed = "
+ + "false but directly calls outbound port "
+ + targetOwner
+ + " (feature-repository-access-permission-contract D7)"));
+ }
+ }
+ }
+ };
+ }
+
+ @ArchTest
+ static final ArchRule APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS =
+ methods()
+ .that()
+ .areDeclaredInClassesThat()
+ .resideInAPackage("..application..")
+ .and()
+ .arePublic()
+ .should()
+ .notHaveRawParameterTypes(
+ DescribedPredicate.describe(
+ "any in ..adapter.inbound.web..dto..",
+ (List params) -> {
+ for (JavaClass p : params) {
+ if (JavaClass.Predicates.resideInAPackage("..adapter.inbound.web..dto..")
+ .test(p)) {
+ return true;
+ }
+ }
+ return false;
+ }))
+ .as(
+ "application methods must not accept request/response DTOs — "
+ + "controllers must translate to Command/Query before invoking the use case "
+ + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)")
+ .allowEmptyShould(true);
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java
new file mode 100644
index 00000000..8dadbe46
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java
@@ -0,0 +1,235 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Every declared {@code @ArchTest} rule is actually discovered and run (BOOT-012).
+ *
+ *
This exists because of what happened when the obvious tidy-up was attempted. {@code
+ * CleanArchitectureTest} was one 2,792-line class holding 75 of this package's 87 rules, and the
+ * natural fix looked like grouping them into nested classes. A throwaway probe established that
+ * ArchUnit's test engine does not discover {@code @ArchTest} fields inside a nested class
+ * of an {@code @AnalyzeClasses} class: the build went green, no test report was produced, and the
+ * rule simply never ran.
+ *
+ *
That is the worst failure a verification suite can have. Splitting the class that way would
+ * have silently switched off 75 architecture rules while every build stayed green — and nothing in
+ * the repository would have noticed, because a rule that does not run reports no violation.
+ *
+ *
With this guard in place the split was then done the safe way: five top-level
+ * {@code @AnalyzeClasses} suites — domain purity, application boundary, adapter boundary, type
+ * boundary, forbidden APIs — plus {@code CleanArchitectureRuleSupport} for the two helpers more
+ * than one of them uses. This test is what proved nothing was lost in the move.
+ *
+ *
So the split is not the thing to guard; the guard is the thing to build first. This test pins
+ * both halves of the contract:
+ *
+ *
+ *
the exact set of rule names that exist, so deleting one is a decision rather than an edit;
+ *
that no rule is declared where the engine cannot see it, so a future reorganisation fails
+ * loudly instead of quietly disarming the suite.
+ *
+ *
+ *
With this in place the class can be split — into separate top-level
+ * {@code @AnalyzeClasses} classes, which share ArchUnit's import cache because the annotation
+ * values match, and which this test verifies still carry every rule.
+ */
+class ArchRuleDiscoveryContractTest {
+
+ /**
+ * {@code @ArchTest} followed by the field declaration, with the indentation that precedes it.
+ *
+ *
Comment lines between the annotation and the declaration are skipped, and that is not a
+ * cosmetic allowance: the first version of this pattern required the declaration on the next line
+ * and therefore did not see {@code SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES},
+ * whose nine-line "known defect" note sits in between. A rule could have hidden from this guard
+ * by being commented — which is precisely the kind of rule most worth keeping an eye on.
+ */
+ private static final Pattern ARCH_TEST_FIELD =
+ Pattern.compile(
+ "(?m)^([ \\t]*)@ArchTest[ \\t]*\\n"
+ + "(?:[ \\t]*(?://[^\\n]*|/\\*(?:[^*]|\\*(?!/))*\\*/)[ \\t]*\\n)*"
+ + "[ \\t]*(?:static\\s+)?final\\s+\\w+\\s+(\\w+)");
+
+ /**
+ * Every rule the architecture suite declares today.
+ *
+ *
Every {@code @AnalyzeClasses} class in this package, not just the large one — the guard is
+ * about rules being discovered, and that is not a property of any single file.
+ *
+ *
Pinned by name rather than counted. A count would be satisfied by deleting one rule and
+ * adding another, which is exactly the substitution a reviewer would want to see spelled out.
+ */
+ private static final List DECLARED_RULES =
+ List.of(
+ "AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC",
+ "APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY",
+ "APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT",
+ "APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT",
+ "APPLICATION_DOES_NOT_DEPEND_ON_OPTIONAL_ADAPTERS",
+ "APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM",
+ "APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION",
+ "APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK",
+ "APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS",
+ "BOOTSTRAP_USES_ONLY_THE_PERSISTENCE_EXPORT_SURFACE",
+ "BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS",
+ "COMPOSITION_ROOT_DOES_NOT_IMPLEMENT_APPLICATION_OR_DOMAIN_PORTS",
+ "CONFIGURATION_PROPERTIES_END_WITH_SETTINGS_OR_POLICY",
+ "CONTACT_POINT_VALUES_ARE_IMMUTABLE_RECORDS",
+ "CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES",
+ "CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES",
+ "CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122",
+ "DOMAIN_CONTEXT_PROPAGATION_PRIMITIVES_STAY_UNSHIPPED",
+ "DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS",
+ "DOMAIN_EVENTS_ARE_RECORDS",
+ "DOMAIN_EVENTS_ARE_TRANSPORT_FREE",
+ "DOMAIN_HAS_NO_LOGGER",
+ "DOMAIN_IS_PURE",
+ "EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY",
+ "IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP",
+ "INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES",
+ "INBOUND_ADAPTERS_DO_NOT_DEPEND_ON_OUTBOUND_ADAPTERS",
+ "INBOUND_PORT_IMPLEMENTATIONS_DECLARE_A_CAPABILITY",
+ "INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY",
+ "INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE",
+ "MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS",
+ "MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES",
+ "MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS",
+ "MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION",
+ "NOTIFICATION_ADAPTER_DOES_NOT_REACH_INTO_PERSISTENCE_OR_TRANSPORT",
+ "NOTIFICATION_ADAPTER_PACKAGES_ARE_ACYCLIC",
+ "NOTIFICATION_APPLICATION_CONTRACT_HAS_NO_PROVIDER_SDK",
+ "NOTIFICATION_APPLICATION_CONTRACT_HAS_NO_TRANSPORT_OR_PERSISTENCE",
+ "NOTIFICATION_CONTRACT_HAS_NO_DIAGNOSTIC_FRAMEWORK",
+ "NOTIFICATION_PLATFORM_PACKAGES_ARE_ACYCLIC",
+ "NOTIFICATION_PLATFORM_PACKAGE_EDGES_ARE_REGISTERED",
+ "NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER",
+ "NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR",
+ "NO_INHERITABLE_THREAD_LOCAL",
+ "NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL",
+ "NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR",
+ "NO_LONG_ID_PK",
+ "NO_MATH_RANDOM_FOR_ID",
+ "NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING",
+ "NO_PROBLEM_DETAIL_USAGE",
+ "NO_REFRESH_SCOPE_ANYWHERE",
+ "NO_RESPONSE_BODY_EMITTER",
+ "NO_SSE_EMITTER",
+ "NO_UUID_RANDOM_IN_CONTROLLER",
+ "NO_VARCHAR_255_FOR_ID_COLUMN",
+ "NO_WEBSOCKET_HANDLER",
+ "ONLY_THE_PERSISTENCE_ADAPTER_HOLDS_A_MONGO_HANDLE",
+ "ONLY_THE_SECURITY_PACKAGE_HANDLES_KEY_MATERIAL",
+ "OPTIONAL_ADAPTER_BEANS_ARE_GATED_BY_CONDITIONAL_ON_PROPERTY",
+ "ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY",
+ "OUTBOUND_ADAPTERS_ARE_PEERS_SHARING_ONLY_SUPPORT",
+ "OUTBOUND_ADAPTERS_DO_NOT_DEPEND_ON_INBOUND_ADAPTERS",
+ "OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS",
+ "OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES",
+ "OUTBOUND_NOTIFICATION_HOLDS_NO_ADMIN_POLICY",
+ "PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS",
+ "PERSISTENCE_DOES_NOT_DEPEND_ON_APPLICATION_SERVICES",
+ "PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER",
+ "PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS",
+ "PERSISTENCE_RDBMS_STAYS_NEUTRAL_OF_H2",
+ "PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO",
+ "PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES",
+ "QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES",
+ "READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS",
+ "REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS",
+ "SAMPLE_APPLICATION_HAS_NO_SLF4J",
+ "SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN",
+ "SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES",
+ "STARTUP_FAILURES_ARE_RAISED_THROUGH_THE_TYPED_FACTORY",
+ "THROWABLES_ARE_NAMED_EXCEPTION",
+ "USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY",
+ "USE_CASE_PORTS_CARRY_ONE_OPERATION",
+ "VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY",
+ "VALID_CASCADE_DEPTH_AT_MOST_THREE",
+ "VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR",
+ "WEB_ADAPTER_DOES_NOT_DEPEND_ON_PERSISTENCE_OR_OUTBOUND_ADAPTERS",
+ "WEB_DTOS_STAY_IN_WEB_ADAPTER");
+
+ @Test
+ @DisplayName("the architecture suite declares exactly the pinned set of rules")
+ void theSuiteDeclaresThePinnedRules() throws IOException {
+ assertThat(new TreeSet<>(declaredRules().keySet()))
+ .as(
+ "a rule that disappears from this set stopped constraining the codebase. Removing one "
+ + "is a decision — make it here, with the reason, rather than by deleting a field "
+ + "(BOOT-012).")
+ .containsExactlyElementsOf(new TreeSet<>(DECLARED_RULES));
+ }
+
+ @Test
+ @DisplayName("no rule is declared where ArchUnit's engine cannot discover it")
+ void noRuleIsDeclaredInsideANestedClass() throws IOException {
+ List undiscoverable = new ArrayList<>();
+ declaredRules()
+ .forEach(
+ (rule, indentation) -> {
+ if (indentation > 2) {
+ undiscoverable.add(rule);
+ }
+ });
+
+ assertThat(undiscoverable)
+ .as(
+ "ArchUnit's engine does not discover @ArchTest fields inside a nested class — the build "
+ + "goes green, no report is written, and the rule never runs. A rule indented deeper "
+ + "than a top-level class member is almost certainly in one. Split the suite into "
+ + "separate top-level @AnalyzeClasses classes instead; they share the import cache "
+ + "(BOOT-012).")
+ .isEmpty();
+ }
+
+ /** Rule name to the indentation depth of its {@code @ArchTest} annotation, across the package. */
+ private static Map declaredRules() throws IOException {
+ Map rules = new LinkedHashMap<>();
+ Path architecture =
+ repositoryRoot()
+ .resolve("src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture");
+ try (Stream sources = Files.walk(architecture)) {
+ for (Path source : sources.filter(p -> p.toString().endsWith(".java")).sorted().toList()) {
+ String text = Files.readString(source);
+ if (!text.contains("@AnalyzeClasses")) {
+ continue;
+ }
+ Matcher field = ARCH_TEST_FIELD.matcher(text);
+ while (field.find()) {
+ rules.put(field.group(2), field.group(1).length());
+ }
+ }
+ }
+ assertThat(rules)
+ .as("the scan must find the architecture rules, or both checks pass vacuously")
+ .isNotEmpty();
+ return rules;
+ }
+
+ private static Path repositoryRoot() {
+ Path candidate = Path.of("").toAbsolutePath();
+ while (candidate != null
+ && !Files.isRegularFile(candidate.resolve("src/config/architecture/modules.json"))) {
+ candidate = candidate.getParent();
+ }
+ if (candidate == null) {
+ throw new IllegalStateException("could not locate the repository root from the test cwd");
+ }
+ return candidate;
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java
index 14a2b0b0..e73a6b63 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java
@@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test;
* Negative ("violations-as-data") tests proving each architecture rule actually catches the
* violation it claims to. Each test loads the intentional fixture classes under {@code
* dev.caskeleton.bootstrap.architecture.violations.*} and asserts that the corresponding rule from
- * {@link CleanArchitectureTest} reports at least one violation.
+ * the architecture suite that owns it reports at least one violation.
*
*
Without this layer, a rule that silently no-matches in production (e.g. a predicate
* referencing a package nothing in the codebase happens to populate) would pass vacuously — exactly
@@ -158,7 +158,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noRefreshScopeAnywhereCatchesRefreshScopeAnnotation() {
EvaluationResult result =
- CleanArchitectureTest.NO_REFRESH_SCOPE_ANYWHERE.evaluate(REFRESH_SCOPE_FIXTURE_ONLY);
+ ForbiddenApiArchitectureTest.NO_REFRESH_SCOPE_ANYWHERE.evaluate(REFRESH_SCOPE_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as("NO_REFRESH_SCOPE_ANYWHERE must catch RefreshScopeUsingFixture (D3/D10)")
@@ -167,7 +167,8 @@ class ArchitectureViolationFixtureTest {
@Test
void domainIsPureCatchesSpringDependencyInDomainPackage() {
- EvaluationResult result = CleanArchitectureTest.DOMAIN_IS_PURE.evaluate(VIOLATION_CLASSES);
+ EvaluationResult result =
+ DomainPurityArchitectureTest.DOMAIN_IS_PURE.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("DOMAIN_IS_PURE must catch SpringDependentDomainFixture")
@@ -177,8 +178,8 @@ class ArchitectureViolationFixtureTest {
@Test
void applicationDoesNotUseSpringTransactionalAnnotationCatchesViolation() {
EvaluationResult result =
- CleanArchitectureTest.APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION.evaluate(
- VIOLATION_CLASSES);
+ ApplicationBoundaryArchitectureTest.APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION
+ .evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -190,7 +191,7 @@ class ArchitectureViolationFixtureTest {
@Test
void applicationHasNoDiagnosticFrameworkCatchesSlf4jDependency() {
EvaluationResult result =
- CleanArchitectureTest.APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK.evaluate(
+ ApplicationBoundaryArchitectureTest.APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK.evaluate(
APPLICATION_DIAGNOSTIC_FRAMEWORK_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -203,8 +204,8 @@ class ArchitectureViolationFixtureTest {
@Test
void applicationDoesNotDependOnApplicationContextCatchesViolation() {
EvaluationResult result =
- CleanArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT.evaluate(
- VIOLATION_CLASSES);
+ ApplicationBoundaryArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT
+ .evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -216,7 +217,7 @@ class ArchitectureViolationFixtureTest {
@Test
void inboundPortImplementationsEndWithUseCaseCatchesBadlyNamedImpl() {
EvaluationResult result =
- CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE.evaluate(
+ ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE.evaluate(
VIOLATION_CLASSES);
assertThat(result.hasViolation())
@@ -227,8 +228,8 @@ class ArchitectureViolationFixtureTest {
@Test
void inboundPortImplementationsDeclareCapabilityCatchesMissingAnnotation() {
EvaluationResult result =
- CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY.evaluate(
- VIOLATION_CLASSES);
+ ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY
+ .evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -244,8 +245,8 @@ class ArchitectureViolationFixtureTest {
@Test
void readOnlyUseCasesDoNotCallRepositoryWriteMethodsCatchesReadToWriteUpgrade() {
EvaluationResult result =
- CleanArchitectureTest.READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS.evaluate(
- READ_ONLY_REPOSITORY_WRITE_FIXTURE_ONLY);
+ ApplicationBoundaryArchitectureTest.READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS
+ .evaluate(READ_ONLY_REPOSITORY_WRITE_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as(
@@ -257,8 +258,8 @@ class ArchitectureViolationFixtureTest {
@Test
void bulkWriteCapabilityRequiresWriteRepositoryAccessCatchesReadAccessBulk() {
EvaluationResult result =
- CleanArchitectureTest.BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS.evaluate(
- BULK_WRITE_WITHOUT_WRITE_ACCESS_FIXTURE_ONLY);
+ ApplicationBoundaryArchitectureTest.BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS
+ .evaluate(BULK_WRITE_WITHOUT_WRITE_ACCESS_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as(
@@ -271,8 +272,8 @@ class ArchitectureViolationFixtureTest {
@Test
void useCaseCapabilityMatchesTransactionPortBoundaryCatchesMissingWriteBoundary() {
EvaluationResult result =
- CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY.evaluate(
- MISSING_TRANSACTION_BOUNDARY_FIXTURE_ONLY);
+ ApplicationBoundaryArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY
+ .evaluate(MISSING_TRANSACTION_BOUNDARY_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as(
@@ -285,8 +286,8 @@ class ArchitectureViolationFixtureTest {
@Test
void useCaseCapabilityMatchesTransactionPortBoundaryAllowsRootWriteBoundary() {
EvaluationResult result =
- CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY.evaluate(
- ROOT_WRITE_TRANSACTION_BOUNDARY_FIXTURE_ONLY);
+ ApplicationBoundaryArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY
+ .evaluate(ROOT_WRITE_TRANSACTION_BOUNDARY_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as(
@@ -298,8 +299,8 @@ class ArchitectureViolationFixtureTest {
@Test
void sharedContractScopeRuleCatchesDomainSpecificSharedPackage() {
EvaluationResult result =
- CleanArchitectureTest.SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES.evaluate(
- VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES
+ .evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -311,7 +312,8 @@ class ArchitectureViolationFixtureTest {
@Test
void externalOutboundCallsRequireExternalOutboundAllowedCapabilityCatchesUnpermittedCall() {
EvaluationResult result =
- CleanArchitectureTest.EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY
+ ApplicationBoundaryArchitectureTest
+ .EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY
.evaluate(OUTBOUND_WITHOUT_PERMISSION_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -325,7 +327,7 @@ class ArchitectureViolationFixtureTest {
@Test
void requestDtosDoNotSilenceUnknownFieldsCatchesClassLevelIgnoreUnknown() {
EvaluationResult result =
- CleanArchitectureTest.REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS.evaluate(
+ TypeBoundaryArchitectureTest.REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS.evaluate(
VIOLATION_CLASSES);
assertThat(result.hasViolation())
@@ -338,7 +340,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noJacksonLaissezFaireSubtypeValidatorCatchesUnsafeDefaultTyping() {
EvaluationResult result =
- CleanArchitectureTest.NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR.evaluate(
+ ForbiddenApiArchitectureTest.NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR.evaluate(
VIOLATION_CLASSES);
assertThat(result.hasViolation())
@@ -349,7 +351,8 @@ class ArchitectureViolationFixtureTest {
@Test
void noJacksonEnableDefaultTypingCallCatchesActivateCall() {
EvaluationResult result =
- CleanArchitectureTest.NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL.evaluate(
+ VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -361,7 +364,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noInheritableThreadLocalCatchesInheritableThreadLocalField() {
EvaluationResult result =
- CleanArchitectureTest.NO_INHERITABLE_THREAD_LOCAL.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.NO_INHERITABLE_THREAD_LOCAL.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("NO_INHERITABLE_THREAD_LOCAL must catch " + "InheritableThreadLocalFixture (B6)")
@@ -371,7 +374,7 @@ class ArchitectureViolationFixtureTest {
@Test
void controllersDoNotReturnDomainOrEntityTypesCatchesDomainReturningFixture() {
EvaluationResult result =
- CleanArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES.evaluate(
+ TypeBoundaryArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES.evaluate(
VIOLATION_CLASSES);
assertThat(result.hasViolation())
@@ -384,7 +387,7 @@ class ArchitectureViolationFixtureTest {
@Test
void applicationMethodsDoNotAcceptWebDtosCatchesWebDtoAcceptingFixture() {
EvaluationResult result =
- CleanArchitectureTest.APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS.evaluate(
+ ApplicationBoundaryArchitectureTest.APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS.evaluate(
VIOLATION_CLASSES);
assertThat(result.hasViolation())
@@ -397,7 +400,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noProblemDetailUsageCatchesProblemDetailFixture() {
EvaluationResult result =
- CleanArchitectureTest.NO_PROBLEM_DETAIL_USAGE.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.NO_PROBLEM_DETAIL_USAGE.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("NO_PROBLEM_DETAIL_USAGE must catch " + "ProblemDetailUsingFixture (D5)")
@@ -407,7 +410,8 @@ class ArchitectureViolationFixtureTest {
@Test
void noMergePatchJsonMediaTypeCatchesMergePatchFixture() {
EvaluationResult result =
- CleanArchitectureTest.NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING.evaluate(
+ VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING must catch " + "MergePatchJsonFixture (B2)")
@@ -417,8 +421,8 @@ class ArchitectureViolationFixtureTest {
@Test
void outboundAdapterMethodReturnsOnlyDomainCatchesRawLeakingFixture() {
EvaluationResult result =
- CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate(
- VIOLATION_CLASSES);
+ TypeBoundaryArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES
+ .evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -430,8 +434,8 @@ class ArchitectureViolationFixtureTest {
@Test
void outboundAdapterMethodRuleCannotBeBypassedWithSettingsSuffix() {
EvaluationResult result =
- CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate(
- B7_SETTINGS_SUFFIX_BYPASS_ONLY);
+ TypeBoundaryArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES
+ .evaluate(B7_SETTINGS_SUFFIX_BYPASS_ONLY);
assertThat(result.hasViolation())
.as("B7 must catch a raw adapter return even when the owner ends with Settings")
@@ -441,8 +445,8 @@ class ArchitectureViolationFixtureTest {
@Test
void outboundAdapterMethodRuleCannotBeBypassedWithActivationPackage() {
EvaluationResult result =
- CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate(
- B7_ACTIVATION_PACKAGE_BYPASS_ONLY);
+ TypeBoundaryArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES
+ .evaluate(B7_ACTIVATION_PACKAGE_BYPASS_ONLY);
assertThat(result.hasViolation())
.as(
@@ -454,7 +458,7 @@ class ArchitectureViolationFixtureTest {
@Test
void outboundAdapterMethodReturnsOnlyDomainCatchesGenericAdapterLeakInIsolation() {
EvaluationResult result =
- CleanArchitectureTest
+ ForbiddenApiArchitectureTest
.MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS
.evaluate(GENERIC_ADAPTER_LEAK_ONLY);
@@ -468,7 +472,8 @@ class ArchitectureViolationFixtureTest {
@Test
void controllerRequestMappingsFollowAip122CatchesKebabPathFixture() {
EvaluationResult result =
- CleanArchitectureTest.CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122.evaluate(
+ VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -480,7 +485,7 @@ class ArchitectureViolationFixtureTest {
@Test
void validCascadeDepthCatchesDeepCascadeFixture() {
EvaluationResult result =
- CleanArchitectureTest.VALID_CASCADE_DEPTH_AT_MOST_THREE.evaluate(VIOLATION_CLASSES);
+ TypeBoundaryArchitectureTest.VALID_CASCADE_DEPTH_AT_MOST_THREE.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("VALID_CASCADE_DEPTH_AT_MOST_THREE must catch " + "DeepCascadeRequestFixture (B4)")
@@ -493,7 +498,7 @@ class ArchitectureViolationFixtureTest {
void validationConstraintsStayAtWebBoundaryCatchesJakartaValidationInDomain() {
// Isolated corpus: proves the "..domain.." glob fires on its own.
EvaluationResult result =
- CleanArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate(
+ TypeBoundaryArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate(
VALIDATION_IN_DOMAIN_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -508,7 +513,7 @@ class ArchitectureViolationFixtureTest {
// Isolated corpus: proves the "..application.." glob fires on its own — not vacuously
// via the domain fixture also present in the shared violations tree.
EvaluationResult result =
- CleanArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate(
+ TypeBoundaryArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate(
VALIDATION_IN_APPLICATION_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -522,7 +527,8 @@ class ArchitectureViolationFixtureTest {
@Test
void noSseEmitterCatchesSseEmitterFixture() {
- EvaluationResult result = CleanArchitectureTest.NO_SSE_EMITTER.evaluate(VIOLATION_CLASSES);
+ EvaluationResult result =
+ ForbiddenApiArchitectureTest.NO_SSE_EMITTER.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("NO_SSE_EMITTER must catch SseEmitterUsingFixture (D3)")
@@ -532,7 +538,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noResponseBodyEmitterCatchesResponseBodyEmitterFixture() {
EvaluationResult result =
- CleanArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("NO_RESPONSE_BODY_EMITTER must catch ResponseBodyEmitterUsingFixture (D3)")
@@ -543,7 +549,7 @@ class ArchitectureViolationFixtureTest {
void noWebsocketHandlerCatchesSpringWebsocketFixture() {
// Isolated corpus: proves the "org.springframework.web.socket.." glob fires on its own.
EvaluationResult result =
- CleanArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(SPRING_WEBSOCKET_FIXTURE_ONLY);
+ ForbiddenApiArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(SPRING_WEBSOCKET_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as("NO_WEBSOCKET_HANDLER must catch SpringWebSocketHandlerFixture (D3)")
@@ -555,7 +561,7 @@ class ArchitectureViolationFixtureTest {
// Isolated corpus: proves the "jakarta.websocket.." glob fires on its own — not
// vacuously via the spring fixture also present in the shared violations tree.
EvaluationResult result =
- CleanArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(JAKARTA_WEBSOCKET_FIXTURE_ONLY);
+ ForbiddenApiArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(JAKARTA_WEBSOCKET_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as("NO_WEBSOCKET_HANDLER must catch JakartaWebSocketEndpointFixture (D3)")
@@ -567,7 +573,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noSseEmitterDoesNotCatchStreamingResponseBody() {
EvaluationResult result =
- CleanArchitectureTest.NO_SSE_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES);
+ ForbiddenApiArchitectureTest.NO_SSE_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -580,7 +586,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noResponseBodyEmitterDoesNotCatchStreamingResponseBody() {
EvaluationResult result =
- CleanArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES);
+ ForbiddenApiArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -593,7 +599,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noWebsocketHandlerDoesNotCatchStreamingResponseBody() {
EvaluationResult result =
- CleanArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(ALLOWED_STREAMING_CLASSES);
+ ForbiddenApiArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(ALLOWED_STREAMING_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -609,7 +615,7 @@ class ArchitectureViolationFixtureTest {
@Test
void noBigdecimalDoubleConstructorCatchesDoubleAndFloatConstructors() {
EvaluationResult result =
- CleanArchitectureTest.NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR.evaluate(VIOLATION_CLASSES);
+ ForbiddenApiArchitectureTest.NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -623,7 +629,7 @@ class ArchitectureViolationFixtureTest {
@Test
void queryPortsPurityRuleCatchesRawDomainReturn() {
EvaluationResult result =
- CleanArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate(
+ TypeBoundaryArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate(
RAW_LEAK_QUERY_PORT_ONLY);
assertThat(result.hasViolation())
@@ -638,7 +644,7 @@ class ArchitectureViolationFixtureTest {
// Proves the rule inspects generic type arguments (List) — a
// raw-return-type check would pass this vacuously.
EvaluationResult result =
- CleanArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate(
+ TypeBoundaryArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate(
GENERIC_LEAK_QUERY_PORT_ONLY);
assertThat(result.hasViolation())
@@ -651,7 +657,7 @@ class ArchitectureViolationFixtureTest {
@Test
void queryPortsPurityRuleDoesNotFlagCleanProjectionPort() {
EvaluationResult result =
- CleanArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate(
+ TypeBoundaryArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate(
CLEAN_PROJECTION_QUERY_PORT_ONLY);
assertThat(result.hasViolation())
@@ -666,7 +672,7 @@ class ArchitectureViolationFixtureTest {
@Test
void domainHasNoLoggerCatchesLoggerInDomainPackage() {
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_HAS_NO_LOGGER.evaluate(VIOLATION_CLASSES);
+ DomainPurityArchitectureTest.DOMAIN_HAS_NO_LOGGER.evaluate(VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as("DOMAIN_HAS_NO_LOGGER must catch LoggerUsingDomainFixture (D3)")
@@ -677,7 +683,7 @@ class ArchitectureViolationFixtureTest {
void valueObjectRuleCatchesAnnotatedPublicNoArgConstructor() {
// Isolated corpus: proves the @ValueObject annotation branch fires on its own.
EvaluationResult result =
- CleanArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate(
+ DomainPurityArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate(
VO_ANNOTATION_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -692,7 +698,7 @@ class ArchitectureViolationFixtureTest {
// Isolated corpus: proves the ..domain.vo.. package branch fires on its own — not
// vacuously via the annotation fixture also present in the shared violations tree.
EvaluationResult result =
- CleanArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate(
+ DomainPurityArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate(
VO_PACKAGE_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -705,7 +711,8 @@ class ArchitectureViolationFixtureTest {
@Test
void aggregateRootSettersAreNotPublicCatchesPublicSetter() {
EvaluationResult result =
- CleanArchitectureTest.AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC.evaluate(VIOLATION_CLASSES);
+ DomainPurityArchitectureTest.AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC.evaluate(
+ VIOLATION_CLASSES);
assertThat(result.hasViolation())
.as(
@@ -717,7 +724,7 @@ class ArchitectureViolationFixtureTest {
@Test
void domainEventsAreRecordsCatchesNonRecordEvent() {
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate(
+ DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate(
NON_RECORD_DOMAIN_EVENT_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -729,7 +736,8 @@ class ArchitectureViolationFixtureTest {
void domainEventsAreRecordsDoesNotFlagARecordEvent() {
// Over-block guard: a @DomainEvent that IS a record must NOT be flagged.
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate(KAFKA_DOMAIN_EVENT_FIXTURE_ONLY);
+ DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate(
+ KAFKA_DOMAIN_EVENT_FIXTURE_ONLY);
assertThat(result.hasViolation())
.as("DOMAIN_EVENTS_ARE_RECORDS must NOT flag KafkaDomainEventFixture — it is a record")
@@ -740,7 +748,7 @@ class ArchitectureViolationFixtureTest {
void domainEventsAreTransportFreeCatchesKafkaType() {
// Isolated corpus: proves the org.apache.kafka.. glob fires on its own.
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
+ DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
KAFKA_DOMAIN_EVENT_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -752,7 +760,7 @@ class ArchitectureViolationFixtureTest {
void domainEventsAreTransportFreeCatchesSpringHttpType() {
// Isolated corpus: proves the org.springframework.http.. glob fires on its own.
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
+ DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
SPRING_HTTP_DOMAIN_EVENT_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -765,7 +773,7 @@ class ArchitectureViolationFixtureTest {
// Isolated corpus: proves the jakarta.ws.rs.. glob fires on its own — not vacuously
// via the kafka or spring fixtures in the shared tree.
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
+ DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
JAXRS_DOMAIN_EVENT_FIXTURE_ONLY);
assertThat(result.hasViolation())
@@ -801,7 +809,7 @@ class ArchitectureViolationFixtureTest {
@Test
void capabilityRuleCatchesAnOrchestrationThatNeverImplementsTheMarker() {
EvaluationResult result =
- CleanArchitectureTest.ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY.evaluate(
+ ApplicationBoundaryArchitectureTest.ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY.evaluate(
UNMARKED_ORCHESTRATION_ONLY);
assertThat(result.hasViolation())
@@ -814,7 +822,7 @@ class ArchitectureViolationFixtureTest {
@Test
void portRuleCatchesAnInboundPortCarryingFourOperations() {
EvaluationResult result =
- CleanArchitectureTest.USE_CASE_PORTS_CARRY_ONE_OPERATION.evaluate(
+ ApplicationBoundaryArchitectureTest.USE_CASE_PORTS_CARRY_ONE_OPERATION.evaluate(
MULTI_OPERATION_PORT_ONLY);
assertThat(result.hasViolation())
@@ -825,8 +833,8 @@ class ArchitectureViolationFixtureTest {
@Test
void controllerRuleCatchesAControllerOutsideAControllerPackage() {
EvaluationResult result =
- CleanArchitectureTest.CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES.evaluate(
- MISFILED_CONTROLLER_ONLY);
+ AdapterBoundaryArchitectureTest.CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES
+ .evaluate(MISFILED_CONTROLLER_ONLY);
assertThat(result.hasViolation())
.as(
@@ -838,8 +846,8 @@ class ArchitectureViolationFixtureTest {
@Test
void entityRuleCatchesAnOrmMappedTypeOutsideAPersistenceAdapter() {
EvaluationResult result =
- CleanArchitectureTest.PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER.evaluate(
- MISFILED_ENTITY_ONLY);
+ TypeBoundaryArchitectureTest.PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER
+ .evaluate(MISFILED_ENTITY_ONLY);
assertThat(result.hasViolation())
.as("what makes a class an entity is the mapping annotation, not the directory")
@@ -849,8 +857,8 @@ class ArchitectureViolationFixtureTest {
@Test
void mapperRuleCatchesAMapperOutsideAMapperPackage() {
EvaluationResult result =
- CleanArchitectureTest.MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES.evaluate(
- MISFILED_PERSISTENCE_MAPPER_ONLY);
+ TypeBoundaryArchitectureTest.MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES
+ .evaluate(MISFILED_PERSISTENCE_MAPPER_ONLY);
assertThat(result.hasViolation())
.as("NotificationRecordMapper sits beside its package, not inside ..mapper..")
@@ -861,7 +869,7 @@ class ArchitectureViolationFixtureTest {
void domainEventsAreTransportFreeDoesNotFlagACleanEvent() {
// Over-block guard: a transport-free @DomainEvent must NOT be flagged.
EvaluationResult result =
- CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
+ DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate(
NON_RECORD_DOMAIN_EVENT_FIXTURE_ONLY);
assertThat(result.hasViolation())
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java
new file mode 100644
index 00000000..5c202774
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java
@@ -0,0 +1,70 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaClass;
+
+/**
+ * Predicates and matchers used by more than one of the split architecture suites.
+ *
+ *
Only what is genuinely shared lives here — two members out of forty-nine. Everything else
+ * moved to the one suite that uses it, because a support class that collects helpers by being
+ * convenient is how the class this split undid got large in the first place (BOOT-012).
+ *
+ *
Package-private rather than private: the suites are siblings in this package, and the members
+ * were private only because they used to have no siblings.
+ */
+final class CleanArchitectureRuleSupport {
+
+ private CleanArchitectureRuleSupport() {}
+
+ /**
+ * A controller is a class Spring routes requests to, wherever it lives.
+ *
+ *
Selecting by the package string {@code ..adapter.inbound.web..controller..} was selecting a
+ * naming convention rather than a role. {@code NotificationCallbackMvcController} sits in {@code
+ * ..web.notification.platform.callback}, so the rules that forbid a controller from touching a
+ * repository or returning an entity never looked at it — the boundary was unguarded for exactly
+ * the endpoint an external provider posts to.
+ */
+ static final DescribedPredicate ARE_CONTROLLERS =
+ new DescribedPredicate<>("annotated as a Spring controller, or filed as one") {
+ @Override
+ public boolean test(JavaClass candidate) {
+ return candidate.isAnnotatedWith("org.springframework.web.bind.annotation.RestController")
+ || candidate.isAnnotatedWith("org.springframework.stereotype.Controller")
+ || candidate.isMetaAnnotatedWith(
+ "org.springframework.web.bind.annotation.RestController")
+ // The package stays in the predicate as well as the annotation. A class filed under
+ // ..controller.. is claiming the role whether or not Spring is currently routing to
+ // it, and dropping the package would narrow a rule this change exists to widen.
+ || (candidate.getPackageName().contains(".adapter.inbound.web")
+ && candidate.getPackageName().contains(".controller"));
+ }
+ };
+
+ static boolean matches(Object value, String mediaType) {
+ if (value == null) {
+ return false;
+ }
+ if (value instanceof String s) {
+ return s.equals(mediaType);
+ }
+ if (value instanceof Object[] arr) {
+ for (Object e : arr) {
+ if (matches(e, mediaType)) {
+ return true;
+ }
+ }
+ }
+ if (value instanceof Iterable> iter) {
+ for (Object e : iter) {
+ if (matches(e, mediaType)) {
+ return true;
+ }
+ }
+ }
+ // Fallback: rendered representation. Catches Spring annotation values that
+ // get wrapped in opaque ArchUnit holders whose toString carries the literal.
+ return value.toString().contains(mediaType);
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
deleted file mode 100644
index 0d42acb9..00000000
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java
+++ /dev/null
@@ -1,2792 +0,0 @@
-package dev.caskeleton.bootstrap.architecture;
-
-import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
-import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
-import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
-import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
-import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
-
-import com.tngtech.archunit.base.DescribedPredicate;
-import com.tngtech.archunit.core.domain.JavaAnnotation;
-import com.tngtech.archunit.core.domain.JavaClass;
-import com.tngtech.archunit.core.domain.JavaClasses;
-import com.tngtech.archunit.core.domain.JavaConstructor;
-import com.tngtech.archunit.core.domain.JavaEnumConstant;
-import com.tngtech.archunit.core.domain.JavaField;
-import com.tngtech.archunit.core.domain.JavaMethod;
-import com.tngtech.archunit.core.domain.JavaMethodCall;
-import com.tngtech.archunit.core.domain.JavaModifier;
-import com.tngtech.archunit.core.importer.ClassFileImporter;
-import com.tngtech.archunit.junit.AnalyzeClasses;
-import com.tngtech.archunit.junit.ArchTest;
-import com.tngtech.archunit.lang.ArchCondition;
-import com.tngtech.archunit.lang.ArchRule;
-import com.tngtech.archunit.lang.ConditionEvents;
-import com.tngtech.archunit.lang.SimpleConditionEvent;
-import dev.caskeleton.application.capability.UseCaseCapability;
-import dev.caskeleton.application.security.RequiresPermission;
-import dev.caskeleton.application.usecase.CommandUseCase;
-import dev.caskeleton.application.usecase.QueryUseCase;
-import dev.caskeleton.domain.identifier.ResourceId;
-import dev.caskeleton.domain.stereotype.AggregateRoot;
-import dev.caskeleton.domain.stereotype.DomainEvent;
-import dev.caskeleton.domain.stereotype.ValueObject;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.regex.Pattern;
-import org.hibernate.type.SqlTypes;
-import org.springframework.boot.health.actuate.endpoint.HealthEndpoint;
-import org.springframework.boot.health.contributor.Health;
-import org.springframework.boot.health.contributor.HealthIndicator;
-
-@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class)
-class CleanArchitectureTest {
-
- @ArchTest
- static final ArchRule DOMAIN_IS_PURE =
- noClasses()
- .that()
- .resideInAPackage("..domain..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "org.springframework..",
- "jakarta.persistence..",
- "javax.persistence..",
- "jakarta.servlet..",
- "org.hibernate..",
- "lombok..",
- "..application..",
- "..adapter..",
- "..bootstrap..",
- "..service..",
- "..infra..",
- "..presentation..",
- "..cmd..")
- .as(
- "domain-core must be framework-neutral POJO — no Spring / JPA / "
- + "Hibernate / Lombok generated bytecode "
- + "(feature-architecture-enforcement-rules D3)")
- .allowEmptyShould(true);
-
- // ---- feature-domain-modeling-guardrails (modeling-specific rules; owner = this branch) ----
- // Domain framework-neutrality is delegated to DOMAIN_IS_PURE above
- // (feature-architecture-enforcement-rules D3). The rules below add the
- // modeling-specific guardrails this branch owns: logger ban (D3), value-object
- // invariant surface (D5/D6), aggregate mutator visibility (D7), and transport-free
- // domain events (D4/D8). They key off the dev.caskeleton.domain.stereotype markers.
-
- @ArchTest
- static final ArchRule DOMAIN_HAS_NO_LOGGER =
- noClasses()
- .that()
- .resideInAPackage("..domain..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "org.slf4j..",
- "java.util.logging..",
- "ch.qos.logback..",
- "org.apache.logging.log4j..")
- .as(
- "D3 DOMAIN_HAS_NO_LOGGER: the domain layer must not depend on a logging "
- + "framework (SLF4J / java.util.logging / Logback / Log4j). An invariant "
- + "violation is expressed as a domain exception carrying a safe reason "
- + "value (noun enum); the application layer translates that into a log "
- + "line. This is a ca-tmpl convention (UNSUPPORTED_DECISION — no official "
- + "standard source), kept SEPARATE from DOMAIN_IS_PURE to preserve the "
- + "owner boundary of feature-architecture-enforcement-rules D3 "
- + "(feature-domain-modeling-guardrails D3, §2)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR =
- classes()
- .that()
- .areAnnotatedWith(ValueObject.class)
- .or()
- .resideInAPackage("..domain.vo..")
- .should(notHaveAPublicNoArgConstructor())
- .as(
- "D5/D6 VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR: a @ValueObject "
- + "(or any type under ..domain.vo..) must validate its invariant on the "
- + "sole construction path and must NOT expose a public no-arg constructor "
- + "— an empty constructor is an invariant-bypassing back door. Records "
- + "with components satisfy this automatically "
- + "(feature-domain-modeling-guardrails D5/D6, §3)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC =
- methods()
- .that()
- .haveNameMatching("set.*")
- .and()
- .areDeclaredInClassesThat()
- .areAnnotatedWith(AggregateRoot.class)
- .should()
- .notBePublic()
- .as(
- "D7 AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC: a @AggregateRoot mutates its "
- + "state only through intention-revealing methods that enforce invariants "
- + "— a raw set* mutator must be package-private/protected (the visibility "
- + "Vernon Option A ORM-external reconstruction relies on). ArchUnit only "
- + "statically reaches the set* surface; other state-changing methods stay "
- + "a code-review concern (feature-domain-modeling-guardrails D7, §4)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule DOMAIN_EVENTS_ARE_RECORDS =
- classes()
- .that()
- .areAnnotatedWith(DomainEvent.class)
- .should(beRecordTypes())
- .as(
- "D4/D8 DOMAIN_EVENTS_ARE_RECORDS: a @DomainEvent is an immutable fact and "
- + "must be declared as a record (feature-domain-modeling-guardrails D4/D8, §5)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule DOMAIN_EVENTS_ARE_TRANSPORT_FREE =
- noClasses()
- .that()
- .areAnnotatedWith(DomainEvent.class)
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("org.apache.kafka..", "org.springframework.http..", "jakarta.ws.rs..")
- .as(
- "D4/D8 DOMAIN_EVENTS_ARE_TRANSPORT_FREE: a @DomainEvent carries domain data "
- + "only and must not depend on a broker/wire/HTTP type (Kafka SDK, Spring "
- + "HTTP, JAX-RS). Translating a domain event into an integration/wire event "
- + "is the application/infrastructure boundary's job. UNSUPPORTED_IMPL_DECISION: "
- + "the forbidden list enumerates only the SDKs ca-tmpl could plausibly add — "
- + "extend it when a new broker/transport is introduced "
- + "(feature-domain-modeling-guardrails D4/D8, §5)")
- .allowEmptyShould(true);
-
- // ---- feature-persistence-auditing-contract D2 (domain stays free of audit metadata) ----
- // DOMAIN_IS_PURE already bans jakarta.persistence.. / org.springframework.. (incl.
- // org.springframework.data..) from ..domain.., so @CreatedDate / @Column / AuditableEntity
- // cannot reach the domain. This rule adds the audit-specific guardrail D2 owns: even as
- // plain POJO fields, a domain aggregate must carry ZERO audit metadata — who/when
- // created/modified is an adapter-persistence concern (AuditableEntity @MappedSuperclass),
- // and putting it in the domain would invert the dependency (the domain would know "who
- // logged in") and leak the infrastructure concern inward.
- @ArchTest
- static final ArchRule DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS =
- noClasses()
- .that()
- .resideInAPackage("..domain..")
- .should(declareAnAuditField())
- .as(
- "D2 DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS: a domain class must not declare "
- + "an audit field (createdAt / updatedAt / createdBy / updatedBy) — audit "
- + "metadata lives only on the adapter-persistence AuditableEntity "
- + "@MappedSuperclass so the domain aggregate carries none of it "
- + "(feature-persistence-auditing-contract D2)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT =
- noClasses()
- .that()
- .resideInAPackage("..application..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "..adapter..",
- "..bootstrap..",
- "..infra..",
- "..presentation..",
- "..cmd..",
- "jakarta.persistence..",
- "javax.persistence..",
- "jakarta.servlet..",
- "org.springframework.web..",
- "org.hibernate..")
- .allowEmptyShould(true);
-
- /**
- * The application never names the messaging platform's types.
- *
- *
The new platform has its own {@code MessagePublisher}, {@code MessageEnvelope} and {@code
- * OutboxStatus}, and the application already has a canonical publish port and outbox model of its
- * own. An import from application code would make the platform's vocabulary the business boundary
- * — and its {@code OutboxStatus.FAILED} means the opposite of the legacy {@code
- * OutboxEventStatus.FAILED}, so the two models cannot be mixed by name without inverting
- * retryable and terminal. The translation belongs in an outbound bridge adapter.
- */
- @ArchTest
- static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM =
- noClasses()
- .that()
- .resideInAPackage("..application..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("dev.caskeleton.messaging..")
- .because(
- "APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM: the application owns its "
- + "publish port and outbox model; a bridge adapter translates, and the two "
- + "outbox status models mean opposite things under the same names")
- .allowEmptyShould(true);
-
- /**
- * The two notification models must not learn about each other.
- *
- *
NOTIF-ADR-005 makes the platform canonical and keeps R1's hundred public types in place
- * without new consumers. Nothing enforced that: {@code Channel}, plan, dispatch and
- * receipt/evidence exist in both namespaces with the same simple names, so a new consumer picks
- * one by import order and the two models drift apart while both claim to be current.
- *
- *
The exception is a single compatibility package. If a bridge is ever needed it goes there,
- * and only there — an exception in several places is not an exception, it is the second canonical
- * model the ADR exists to prevent.
- */
- @ArchTest
- static final ArchRule NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.application.notification.platform..")
- .and()
- .resideOutsideOfPackage("dev.caskeleton.application.notification.compatibility.r1..")
- .should()
- .dependOnClassesThat(
- new com.tngtech.archunit.base.DescribedPredicate<>(
- "reside in the R1 notification namespace") {
- @Override
- public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) {
- String name = candidate.getPackageName();
- return name.equals("dev.caskeleton.application.notification");
- }
- })
- .because(
- "NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER: NOTIF-ADR-005 makes the "
- + "platform canonical; a bridge belongs in "
- + "dev.caskeleton.application.notification.compatibility.r1 and nowhere else")
- .allowEmptyShould(true);
-
- /**
- * A transport must depend on an inbound port, never on the class behind it.
- *
- *
The notification callback controller injected {@code
- * IngestProviderCallbackApplicationUseCase} directly. The existing capability rules did not catch
- * it, and could not: they inspect types that already implement {@code CommandUseCase} or {@code
- * QueryUseCase}, so an orchestration that never adopted the marker is invisible to the very gate
- * that would have required it. This rule looks from the other side — at what the transport
- * depends on — which is where the omission is visible.
- */
- @ArchTest
- static final ArchRule INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.adapter.inbound..")
- .should()
- .dependOnClassesThat(
- new com.tngtech.archunit.base.DescribedPredicate<>(
- "are concrete application orchestration services") {
- @Override
- public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) {
- return candidate.getPackageName().startsWith("dev.caskeleton.application.")
- && !candidate.isInterface()
- && !candidate
- .getModifiers()
- .contains(com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT)
- && (candidate.getSimpleName().endsWith("Service")
- || candidate.getSimpleName().endsWith("Orchestrator"));
- }
- })
- .because(
- "INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES: an entrypoint reached "
- + "through a concrete class bypasses the use-case fitness gate, which is what "
- + "declares transaction mode, repository access and outbound permission")
- .allowEmptyShould(true);
-
- /**
- * An orchestration entrypoint, recognised by what it does rather than by what it implements.
- *
- *
This is the gap NTF-019 names. Every existing capability rule starts from {@code
- * .that().implement(CommandUseCase.class)} — so a new orchestration that simply never implements
- * the marker is not checked by any of them. The rules were not wrong; they were unreachable. A
- * class that opens a transaction and writes through a repository port is an orchestration
- * entrypoint whether or not anybody remembered to say so.
- */
- private static final DescribedPredicate ARE_ORCHESTRATION_ENTRYPOINTS =
- new DescribedPredicate<>("application orchestration entrypoints") {
- @Override
- public boolean test(JavaClass candidate) {
- // ".application." rather than a prefix match, matching the glob convention the rest of
- // this file uses — and, concretely, so the negative fixture that proves this rule fires
- // is itself inside the rule's scope. A rule whose only corpus is production code cannot
- // be shown to fire at all.
- if (!candidate.getPackageName().contains(".application.")
- || candidate.isInterface()
- || candidate.getModifiers().contains(JavaModifier.ABSTRACT)
- || candidate.isRecord()
- || candidate.isEnum()) {
- return false;
- }
- // Holding a transaction port is the tell. A helper that only computes has no boundary
- // to declare; a class that can start a unit of work is deciding its shape.
- //
- // Read from the field types rather than from the dependency graph: an isolated corpus
- // (the negative fixture) resolves the port as a stub, and the dependency edge is then
- // absent — which would make this rule pass on the very shape it exists to catch.
- return candidate.getFields().stream()
- .anyMatch(
- field ->
- field
- .getRawType()
- .getFullName()
- .equals("dev.caskeleton.application.transaction.TransactionPort"));
- }
- };
-
- @ArchTest
- static final ArchRule ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY =
- classes()
- .that(ARE_ORCHESTRATION_ENTRYPOINTS)
- .and()
- .haveSimpleNameEndingWith("UseCase")
- .should()
- .beAnnotatedWith(UseCaseCapability.class)
- .as(
- "NTF-019: a use case that opens a transaction declares its capability. The existing "
- + "rules all start from the marker interface, so an entrypoint that never "
- + "implements it is checked by nothing at all")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule OUTBOUND_NOTIFICATION_HOLDS_NO_ADMIN_POLICY =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.adapter.outbound.notification..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName("dev.caskeleton.application.transaction.TransactionPort")
- .orShould()
- .dependOnClassesThat()
- .haveFullyQualifiedName(
- "dev.caskeleton.application.notification.platform.admin.AdminActor")
- .orShould()
- .dependOnClassesThat()
- .haveFullyQualifiedName(
- "dev.caskeleton.application.notification.platform.admin.NotificationAdminAuthority")
- .as(
- "NTF-020: who may perform an operator action, and where a unit of work begins, are "
- + "decisions about the product. They were both made in the layer whose job is "
- + "speaking provider protocols, where no application test could reach them")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_A_CAPABILITY =
- classes()
- .that(
- new DescribedPredicate("implement an inbound port") {
- @Override
- public boolean test(JavaClass candidate) {
- return !candidate.isInterface()
- && !candidate.getModifiers().contains(JavaModifier.ABSTRACT)
- && candidate.getAllRawInterfaces().stream()
- .anyMatch(
- implemented -> implemented.getPackageName().contains(".port.in"));
- }
- })
- .should()
- .beAnnotatedWith(UseCaseCapability.class)
- .as(
- "NTF-019: implementing an inbound port is what makes a class an entrypoint, whatever "
- + "it is named. The name-based half of this check would miss a class called "
- + "NotificationSubmissionService that a controller reaches directly")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule USE_CASE_PORTS_CARRY_ONE_OPERATION =
- classes()
- .that()
- .resideInAPackage("..application..port.in..")
- .and()
- .areInterfaces()
- .should(declareAtMostOneAbstractOperation())
- .as(
- "NTF-019: one operation per inbound port. NotificationOrchestrator mixed submit, "
- + "schedule, cancel and get, and no single transaction mode is true of all four "
- + "— the compiler agrees, since CommandUseCase cannot be inherited twice with "
- + "different type arguments")
- .allowEmptyShould(true);
-
- /**
- * Hold an inbound port to a single abstract operation.
- *
- * @return the condition
- */
- private static ArchCondition declareAtMostOneAbstractOperation() {
- return new ArchCondition<>("declare at most one abstract operation") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- List abstractMethods =
- item.getMethods().stream()
- .filter(method -> !method.getModifiers().contains(JavaModifier.STATIC))
- .filter(method -> method.getModifiers().contains(JavaModifier.ABSTRACT))
- .toList();
- if (abstractMethods.size() > 1) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- item.getName()
- + " declares "
- + abstractMethods.size()
- + " operations; an inbound port declares one, so its capability can be "
- + "stated honestly"));
- }
- }
- };
- }
-
- @ArchTest
- static final ArchRule APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.application..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "org.slf4j..",
- "java.util.logging..",
- "ch.qos.logback..",
- "org.apache.logging.log4j..",
- "io.micrometer..")
- .as(
- "APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK: application-core owns typed outbound "
- + "diagnostic ports, while logging and metrics implementations belong to "
- + "outbound adapters")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule SAMPLE_APPLICATION_HAS_NO_SLF4J =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.sample.portfolio.application..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("org.slf4j..")
- .as(
- "SAMPLE_APPLICATION_HAS_NO_SLF4J: sample application collaborators read "
- + "correlation context through application-core CorrelationIdPort; inbound "
- + "adapters own MDC")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.sample.portfolio.application..")
- .and()
- .doNotHaveFullyQualifiedName(
- "dev.caskeleton.sample.portfolio.application.poster.UploadPosterImageUseCase")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("dev.caskeleton.application.storage")
- .as(
- "Only the characterized UploadPosterImageUseCase may temporarily import the legacy "
- + "ObjectStoragePort/StoredObject contract. New sample business code must use "
- + "the semantic objectstorage ports. Task 23 moves compatibility into the exact "
- + "legacy slice; Task 24 separately reviews the administrative migration seam.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION =
- noClasses()
- .that()
- .resideInAPackage("..application..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional")
- .as(
- "application package must use TransactionPort instead of "
- + "@Transactional (feature-application-port-usecase-contract D3)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE =
- classes()
- .that()
- .implement(CommandUseCase.class)
- .or()
- .implement(QueryUseCase.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should()
- .haveSimpleNameEndingWith("UseCase")
- .as(
- "CommandUseCase / QueryUseCase implementations must end with 'UseCase' "
- + "(feature-application-port-usecase-contract D1)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY =
- classes()
- .that()
- .implement(CommandUseCase.class)
- .or()
- .implement(QueryUseCase.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should()
- .beAnnotatedWith(UseCaseCapability.class)
- .as(
- "every CommandUseCase / QueryUseCase implementation must declare "
- + "@UseCaseCapability (feature-application-port-usecase-contract §판정 기준)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT =
- noClasses()
- .that()
- .resideInAPackage("..application..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName("org.springframework.context.ApplicationContext")
- .as(
- "application package must not depend on Spring ApplicationContext "
- + "(blocks getBean(Class) reflection-style bypass — "
- + "feature-architecture-enforcement-rules D11). string-key bean "
- + "lookup / Class.forName(String) bypass remains ArchUnit's "
- + "static-analysis limit per D12.")
- .allowEmptyShould(true);
-
- // NOTE: the former `inbound_port_implementations_do_not_declare_keyed_idempotency`
- // freeze rule (feature-application-port-usecase-contract D14) is removed now that
- // feature-rate-limit-idempotency-contract has merged: Idempotency.KEYED is backed
- // by IdempotencyExecutor + IdempotencyStorePort + the idempotency_record table, so a
- // use case may declare it.
-
- // ---- feature-repository-access-permission-contract D12 / D6 (repositoryAccess coherence) ----
-
- @ArchTest
- static final ArchRule READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS =
- classes()
- .that()
- .areAnnotatedWith(UseCaseCapability.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should(notCallRepositoryWriteUnlessWriteRepository())
- .as(
- "D12 READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS: a use case "
- + "whose @UseCaseCapability(repositoryAccess) is not WRITE_REPOSITORY must "
- + "not DIRECTLY call a write method "
- + "(save / saveAll / saveAndFlush / delete* / remove* / update / insert / "
- + "persist / merge / store) of a *Repository port. Declaring READ_REPOSITORY "
- + "(or NONE) then mutating is the silent read-to-write upgrade this contract "
- + "blocks (feature-repository-access-permission-contract D12). "
- + "UNSUPPORTED_IMPL_DECISION: ArchUnit static analysis only reaches DIRECT "
- + "calls — a write reached through a helper / mapper escapes the call graph "
- + "and stays a code-review concern (D12 static-analysis limit). The write "
- + "method-name allowlist is a ca-tmpl convention; extend it when a new "
- + "mutating verb enters the repository vocabulary.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS =
- classes()
- .that()
- .areAnnotatedWith(UseCaseCapability.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should(haveWriteRepositoryAccessWhenBulkWrite())
- .as(
- "D6 BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS: "
- + "@UseCaseCapability(bulkWrite = true) requires repositoryAccess = "
- + "WRITE_REPOSITORY — a bulk write (N > 100) is still a write, and the "
- + "capability registry binds BULK_WRITE to WRITE_REPOSITORY "
- + "(feature-repository-access-permission-contract D6).")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY =
- classes()
- .that()
- .areAnnotatedWith(UseCaseCapability.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should(callTransactionPortMethodRequiredByCapability())
- .as(
- "feature-domain-feature-onboarding-contract D4: a use case that declares a "
- + "repository-backed transaction capability must call the matching "
- + "TransactionPort boundary directly: READ_REPOSITORY+READ_ONLY -> inRead, "
- + "WRITE_REPOSITORY+WRITE -> inWrite or inRootWrite, REQUIRES_NEW -> inNew. "
- + "RepositoryAccess.NONE may intentionally skip a DB transaction. "
- + "UNSUPPORTED_IMPL_DECISION: static analysis reaches direct calls only; a "
- + "transaction hidden behind a helper remains a code-review concern.")
- .allowEmptyShould(true);
-
- // ---- feature-authentication-authorization-contract D1 / D4 (product authorization) ----
-
- @ArchTest
- static final ArchRule MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION =
- classes()
- .that()
- .areAnnotatedWith(UseCaseCapability.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should(declareRequiredPermissionWhenMutating())
- .as(
- "D4 MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION: a use case whose "
- + "@UseCaseCapability(repositoryAccess) is WRITE_REPOSITORY MUST also declare "
- + "@RequiresPermission, so a mutating operation cannot silently ship without a "
- + "permission gate (feature-authentication-authorization-contract D4). The "
- + "skeleton baseline is mutating-only; public reads are exempt. "
- + "UNSUPPORTED_IMPL_DECISION: this rule checks the type-level annotation (the "
- + "@UseCaseCapability convention); a method-only @RequiresPermission on a use "
- + "case still trips it. AOP self-invocation / non-bean entry points stay a "
- + "static-analysis blind spot (D4 limit).")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY =
- noClasses()
- .that()
- .resideInAnyPackage("..application..", "..domain..")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("org.springframework.security..")
- .as(
- "D1 APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY: application-core and "
- + "domain-core must stay free of Spring Security types. The authorization "
- + "DECISION is a framework-free port (@RequiresPermission / AuthorizationPort / "
- + "AuthorizationPrincipal); the Spring-aware enforcement MECHANISM "
- + "(AuthorizationManager) lives only in adapter:inbound:web "
- + "(feature-authentication-authorization-contract D1).")
- .allowEmptyShould(true);
-
- // ---- feature-management-actuator-security-contract D6 (health shape-ownership guardrail) ----
- // ManagementSecurityConfig (in ..management.security..) must NOT import or depend on
- // HealthEndpoint, HealthIndicator, or Health — the health endpoint shape belongs to
- // the runtime-health branch. The security config only matches/permits endpoints by id string;
- // it never inspects health internals.
-
- @ArchTest
- static final ArchRule MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS =
- noClasses()
- .that()
- .resideInAPackage("..management.security..")
- .should()
- .dependOnClassesThat()
- .areAssignableTo(HealthEndpoint.class)
- .orShould()
- .dependOnClassesThat()
- .areAssignableTo(HealthIndicator.class)
- .orShould()
- .dependOnClassesThat()
- .areAssignableTo(Health.class)
- .as(
- "D6 MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS: classes in "
- + "..management.security.. must not import or depend on HealthEndpoint, "
- + "HealthIndicator, or Health — the health endpoint shape is owned "
- + "by the runtime-health branch. The actuator security config matches "
- + "endpoints by id only, never by health internals "
- + "(feature-management-actuator-security-contract D6).")
- .allowEmptyShould(true);
-
- private static final Set REPOSITORY_WRITE_METHOD_NAMES =
- Set.of(
- "save",
- "saveAll",
- "saveAndFlush",
- "delete",
- "deleteById",
- "deleteAll",
- "remove",
- "removeAll",
- "removeById",
- "update",
- "insert",
- "persist",
- "merge",
- "store");
-
- private static ArchCondition notCallRepositoryWriteUnlessWriteRepository() {
- return new ArchCondition<>(
- "not call a *Repository write method unless " + "repositoryAccess = WRITE_REPOSITORY") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
- Object repositoryAccess = annotation.get("repositoryAccess").orElse(null);
- boolean writeRepository =
- repositoryAccess instanceof JavaEnumConstant constant
- && "WRITE_REPOSITORY".equals(constant.name());
- if (writeRepository) {
- return; // WRITE_REPOSITORY use cases are permitted to mutate.
- }
- for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
- String targetOwner = call.getTargetOwner().getSimpleName();
- String targetName = call.getName();
- if (targetOwner.endsWith("Repository")
- && REPOSITORY_WRITE_METHOD_NAMES.contains(targetName)) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " declares repositoryAccess != "
- + "WRITE_REPOSITORY but directly calls "
- + targetOwner
- + "."
- + targetName
- + "(...) — a repository write "
- + "(feature-repository-access-permission-contract D12)"));
- }
- }
- }
- };
- }
-
- private static ArchCondition declareRequiredPermissionWhenMutating() {
- return new ArchCondition<>(
- "declare @RequiresPermission when repositoryAccess = WRITE_REPOSITORY") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
- Object repositoryAccess = annotation.get("repositoryAccess").orElse(null);
- boolean writeRepository =
- repositoryAccess instanceof JavaEnumConstant constant
- && "WRITE_REPOSITORY".equals(constant.name());
- if (!writeRepository) {
- return;
- }
- if (!item.isAnnotatedWith(RequiresPermission.class.getName())) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " declares @UseCaseCapability(repositoryAccess = "
- + "WRITE_REPOSITORY) but is missing @RequiresPermission — a mutating use "
- + "case must declare the permission it requires "
- + "(feature-authentication-authorization-contract D4)"));
- }
- }
- };
- }
-
- private static ArchCondition haveWriteRepositoryAccessWhenBulkWrite() {
- return new ArchCondition<>(
- "declare repositoryAccess = WRITE_REPOSITORY when bulkWrite = true") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
- boolean bulkWrite = Boolean.TRUE.equals(annotation.get("bulkWrite").orElse(false));
- if (!bulkWrite) {
- return;
- }
- Object repositoryAccess = annotation.get("repositoryAccess").orElse(null);
- boolean writeRepository =
- repositoryAccess instanceof JavaEnumConstant constant
- && "WRITE_REPOSITORY".equals(constant.name());
- if (!writeRepository) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " declares @UseCaseCapability(bulkWrite = true) "
- + "without repositoryAccess = WRITE_REPOSITORY "
- + "(feature-repository-access-permission-contract D6)"));
- }
- }
- };
- }
-
- @SuppressWarnings("resource")
- private static ArchCondition callTransactionPortMethodRequiredByCapability() {
- return new ArchCondition<>("call the TransactionPort method required by @UseCaseCapability") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
- String transactionMode = enumAnnotationValue(annotation, "transactionMode");
- String repositoryAccess = enumAnnotationValue(annotation, "repositoryAccess");
-
- Set requiredMethods = Set.of();
- if ("REQUIRES_NEW".equals(transactionMode)) {
- requiredMethods = Set.of("inNew");
- } else if ("WRITE".equals(transactionMode) && "WRITE_REPOSITORY".equals(repositoryAccess)) {
- requiredMethods = Set.of("inWrite", "inRootWrite");
- } else if ("READ_ONLY".equals(transactionMode)
- && "READ_REPOSITORY".equals(repositoryAccess)) {
- requiredMethods = Set.of("inRead");
- }
-
- if (requiredMethods.isEmpty()) {
- return;
- }
-
- for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
- if ("dev.caskeleton.application.transaction.TransactionPort"
- .equals(call.getTargetOwner().getFullName())
- && requiredMethods.contains(call.getName())) {
- return;
- }
- }
-
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " declares @UseCaseCapability(transactionMode = "
- + transactionMode
- + ", repositoryAccess = "
- + repositoryAccess
- + ") but does not directly call one of TransactionPort."
- + requiredMethods
- + "(...)"));
- }
- };
- }
-
- private static String enumAnnotationValue(JavaAnnotation> annotation, String property) {
- Object value = annotation.get(property).orElse(null);
- return value instanceof JavaEnumConstant constant ? constant.name() : "";
- }
-
- private static ArchCondition resideInAllowedSharedContractPackage() {
- return new ArchCondition<>("reside in an allowed shared operational contract package") {
- private final Set allowedPackages =
- Set.of(
- "dev.caskeleton.shared",
- "dev.caskeleton.shared.response",
- "dev.caskeleton.shared.request",
- "dev.caskeleton.shared.error",
- "dev.caskeleton.shared.operation",
- "dev.caskeleton.shared.headers",
- "dev.caskeleton.shared.logging",
- "dev.caskeleton.shared.tracing",
- "dev.caskeleton.shared.metrics",
- "dev.caskeleton.shared.registry",
- "dev.caskeleton.shared.annotation",
- // Not this module at all — see the defect note on the rule above. The Mongo leaf has
- // a
- // tenancy.shared package of its own and only began tripping this rule when that leaf
- // joined the composition root's analysis scope.
- "dev.caskeleton.adapter.outbound.mongo.advanced.tenancy.shared",
- // The five master switches and their strict parse. Operational rather than domain:
- // it names environment keys and rejects unreadable values, and it lives here because
- // the environment post-processor needs it before a Spring context exists, so it must
- // not depend on Spring.
- "dev.caskeleton.shared.activation",
- // feature-authentication-authorization-contract: the Permission
- // (resource:action) value object is a Java-only operational
- // authorization contract consumed by AuthorizationPort.
- "dev.caskeleton.shared.security",
- // feature-runtime-context-propagation-contract: the domain-context
- // propagation port + default ThreadLocal strategy + factory
- // (swappable abstraction, the rate-limit pattern).
- "dev.caskeleton.shared.concurrency");
-
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- String packageName = item.getPackageName();
- boolean allowed = false;
- for (String allowedPackage : allowedPackages) {
- if (packageName.equals(allowedPackage) || packageName.startsWith(allowedPackage + ".")) {
- allowed = true;
- break;
- }
- }
- if (!allowed) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " resides in "
- + packageName
- + ", which is not an allowed shared operational contract package"));
- }
- }
- };
- }
-
- // ---- feature-repository-access-permission-contract D7 (outbound capability coherence) ----
-
- /**
- * Outbound ports are the interfaces a use case must hold {@code externalOutboundAllowed} to call.
- * They are identified by their adapter binding — any interface implemented by a production class
- * residing under {@code ..adapter.outbound..} (e.g. RepoStatsPort ← RepoStatsPortClient),
- * EXCLUDING the {@code ..adapter.outbound.identifier..} sub-package: UUIDv7 id/event-id factories
- * are a non-IO driven adapter (in-process generation/codec, no external HTTP / message / cache
- * call — see IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP), so calling one
- * directly does not require {@code externalOutboundAllowed = true}. Also EXCLUDING the {@code
- * ..adapter.outbound.persistence..} sub-package: repositories/stores are internal (driven)
- * outbound adapters — a use case calling a repository does not require {@code
- * externalOutboundAllowed = true}, and their package moved under {@code adapter.outbound} only as
- * a topology rename (Task 7 owns the holistic external-vs-internal split). Precomputed from
- * production classes (tests excluded) so the coherence rule has no application-side marker
- * dependency.
- */
- private static final Set OUTBOUND_PORT_NAMES = outboundPortNames();
-
- private static Set outboundPortNames() {
- JavaClasses production =
- new ClassFileImporter()
- .withImportOption(new ProductionClassImportOption())
- .importPackages("dev.caskeleton");
- Set names = new HashSet<>();
- for (JavaClass type : production) {
- if (type.getPackageName().contains(".adapter.outbound.")
- && !isInProcessDrivenOutbound(type.getPackageName())) {
- for (JavaClass iface : type.getAllRawInterfaces()) {
- names.add(iface.getFullName());
- }
- }
- }
- return names;
- }
-
- /**
- * True when {@code packageName} is an IN-PROCESS (non-IO) driven outbound concern — {@code
- * ..adapter.outbound.persistence..} (repositories / stores) or {@code
- * ..adapter.outbound.identifier..} (UUIDv7 id/event-id generation & codec). Both nest under
- * {@code ..adapter.outbound..} as a Task-1–6 topology rename but perform NO external IO (no HTTP
- * / message / cache network call), so an interface they implement is NOT an external outbound
- * port and a use case calling one directly does not require {@code externalOutboundAllowed =
- * true}. This single predicate consolidates the two per-package {@code .contains(...)} exclusions
- * that Tasks 2/6 added locally to {@link #outboundPortNames()}.
- *
- *
The genuinely external-IO outbound adapters — {@code httpclient}, {@code messaging}, {@code
- * notification}, and {@code cache} — are deliberately NOT treated as in-process here: a Redis
- * cache call is a real network dependency, so the D7 external-outbound gate must keep covering
- * it. Folding cache/support into this exclusion (as an earlier plan sketch suggested) would
- * weaken that gate for no current benefit and contradict this heuristic's own IO-vs-in- process
- * rationale, so it is intentionally NOT done.
- */
- private static boolean isInProcessDrivenOutbound(String packageName) {
- return packageName.contains(".adapter.outbound.persistence")
- || packageName.contains(".adapter.outbound.identifier");
- }
-
- @ArchTest
- static final ArchRule EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY =
- classes()
- .that()
- .areAnnotatedWith(UseCaseCapability.class)
- .and()
- .areNotInterfaces()
- .and()
- .areNotAnonymousClasses()
- .and()
- .areNotMemberClasses()
- .should(notCallOutboundPortUnlessAllowed())
- .as(
- "D7 EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY: a "
- + "use case that DIRECTLY calls an outbound port (an interface implemented "
- + "by a production class under ..adapter.outbound..) must declare "
- + "@UseCaseCapability(externalOutboundAllowed = true). Without it, emitting "
- + "an external HTTP / message / cache call is forbidden "
- + "(feature-repository-access-permission-contract D7). Outbound ports are "
- + "identified by their adapter binding (RepoStatsPort ← RepoStatsPortClient), "
- + "so no application-side marker is required. UNSUPPORTED_IMPL_DECISION: "
- + "ArchUnit reaches DIRECT calls only — a call routed through a helper / "
- + "mapper escapes the call graph and stays a code-review concern "
- + "(static-analysis limit, mirrors D12).")
- .allowEmptyShould(true);
-
- private static ArchCondition notCallOutboundPortUnlessAllowed() {
- return new ArchCondition<>("not call an outbound port unless externalOutboundAllowed = true") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- JavaAnnotation> annotation = item.getAnnotationOfType(UseCaseCapability.class.getName());
- boolean allowed =
- Boolean.TRUE.equals(annotation.get("externalOutboundAllowed").orElse(false));
- if (allowed) {
- return; // externalOutboundAllowed use cases may emit outbound calls.
- }
- for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
- String targetOwner = call.getTargetOwner().getFullName();
- if (OUTBOUND_PORT_NAMES.contains(targetOwner)) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " declares externalOutboundAllowed = "
- + "false but directly calls outbound port "
- + targetOwner
- + " (feature-repository-access-permission-contract D7)"));
- }
- }
- }
- };
- }
-
- @ArchTest
- static final ArchRule WEB_ADAPTER_DOES_NOT_DEPEND_ON_PERSISTENCE_OR_OUTBOUND_ADAPTERS =
- noClasses()
- .that()
- .resideInAPackage("..adapter.inbound.web..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "..adapter.outbound.persistence..",
- "..adapter.outbound..",
- "..adapter.outbound.identifier..",
- "org.springframework.data.repository..",
- "org.springframework.data.jpa.repository..",
- "jakarta.persistence..",
- "javax.persistence..",
- "org.hibernate..");
-
- @ArchTest
- static final ArchRule PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS =
- noClasses()
- .that()
- .resideInAPackage("..adapter.outbound.persistence..")
- .should()
- // persistence now nests under adapter.outbound (topology rename), so the forbidden
- // outbound target must exclude persistence's own package — otherwise the abundant
- // intra-persistence references (adapter -> entity/mapper/repository) self-overlap.
- // Mirrors the IDENTIFIER_ADAPTER_... predicate-form self-exclusion below.
- .dependOnClassesThat(
- JavaClass.Predicates.resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..")
- .or(
- JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
- .and(
- JavaClass.Predicates.resideOutsideOfPackage(
- "..adapter.outbound.persistence.."))))
- .as(
- "persistence-jpa is an internal (driven) outbound adapter — it must not reach into "
- + "the web adapter, sibling outbound adapters, or the composition root "
- + "(intra-persistence references are allowed).")
- .allowEmptyShould(true);
-
- // ---- persistence-rdbms/no-sql-extensibility (RDBMS base vendor-neutrality) ----
- // adapter-persistence-rdbms (all of ..adapter.outbound.persistence.. EXCEPT
- // ..adapter.outbound.persistence.postgresql..)
- // must not import PostgreSQL driver or flyway-database-postgresql types. Those vendor-specific
- // dependencies belong only in adapter-persistence-postgresql.
- //
- // 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
- * called its roll-up to work out what a request's status should be — so "one recipient completed
- * and one failed means partially completed", a product rule, lived behind a JPA store, and
- * changing it meant editing the application and the database layer in step.
- *
- *
The test is concreteness, not naming. An application interface named {@code ...Service} —
- * {@code FileQuotaService}, for instance — is a port: the adapter implements it, so the
- * dependency points from infrastructure to a contract, which is the direction this architecture
- * wants. A concrete application service is orchestration, and an adapter that calls one has taken
- * over a decision the use case owns.
- */
- @ArchTest
- static final ArchRule PERSISTENCE_DOES_NOT_DEPEND_ON_APPLICATION_SERVICES =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
- .should()
- .dependOnClassesThat(
- new com.tngtech.archunit.base.DescribedPredicate<>(
- "application services or use cases") {
- @Override
- public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) {
- String name = candidate.getName();
- return name.startsWith("dev.caskeleton.application.")
- && !candidate.isInterface()
- && (candidate.getSimpleName().endsWith("Service")
- || candidate.getSimpleName().endsWith("UseCase"));
- }
- })
- .as(
- "adapter:outbound:persistence-jpa must not depend on application services or use "
- + "cases: an adapter that calls one has taken over a decision the use case owns.")
- .allowEmptyShould(true);
-
- static final ArchRule PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
- .and()
- .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("org.postgresql..", "org.flywaydb.database.postgresql..")
- .as(
- "adapter:outbound:persistence-jpa (non-postgresql) must stay vendor-neutral: "
- + "PostgreSQL driver / flyway-database-postgresql types live only in the "
- + ".postgresql subpackage of adapter:outbound:persistence-jpa.")
- .allowEmptyShould(true);
-
- // The same fence around the second vendor. H2 arrives as a runtimeOnly driver, so today nothing
- // imports org.h2.. and this rule holds vacuously — which is the point of writing it now: the
- // first class that reaches for an H2 type has to do it inside the vendor package, before the
- // "vendor-neutral base" claim quietly stops being true.
- @ArchTest
- static final ArchRule PERSISTENCE_RDBMS_STAYS_NEUTRAL_OF_H2 =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
- .and()
- .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.h2..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("org.h2..")
- .as(
- "adapter:outbound:persistence-jpa (non-h2) must stay vendor-neutral: H2 driver types "
- + "live only in the .h2 subpackage of adapter:outbound:persistence-jpa.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS =
- fields()
- .that()
- .areAnnotatedWith("jakarta.persistence.Column")
- .and()
- .areDeclaredInClassesThat()
- .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..")
- .and()
- .areDeclaredInClassesThat()
- .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..")
- .should(notDeclareColumnDefinition())
- .as(
- "adapter:outbound:persistence-jpa (non-postgresql) must not pin vendor SQL "
- + "columnDefinition strings; RDBMS physical types belong to vendor Flyway "
- + "migrations, while rdbms entities may use portable JPA/Hibernate type hints.")
- .allowEmptyShould(true);
-
- // NOTE: adapter.outbound.identifier AND adapter.outbound.persistence are both nested under
- // adapter.outbound (sibling adapters, not top-level packages), so the rules below exclude each
- // one's own package from the side that would otherwise match it against itself — mirroring the
- // resideOutsideOfPackage sibling-exclusion style already used for
- // PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL / PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES
- // above. Without this, an intra-identifier or intra-persistence reference (a class referencing a
- // class in its own nested adapter) would spuriously violate one of the rules.
-
- @ArchTest
- static final ArchRule OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS =
- noClasses()
- .that()
- .resideInAPackage("..adapter.outbound..")
- .and()
- .resideOutsideOfPackage("..adapter.outbound.identifier..")
- // persistence-jpa is itself a nested outbound adapter; exclude its own package from the
- // selector so its intra-references (which land in the ..adapter.outbound.persistence..
- // forbidden target below) do not self-overlap. Generic outbound adapters (messaging,
- // cache, http, ...) are still forbidden from depending on persistence.
- .and()
- .resideOutsideOfPackage("..adapter.outbound.persistence..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "..adapter.inbound.web..",
- "..adapter.outbound.persistence..",
- "..adapter.outbound.identifier..",
- "..bootstrap..")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP =
- noClasses()
- .that()
- .resideInAPackage("..adapter.outbound.identifier..")
- .should()
- .dependOnClassesThat(
- JavaClass.Predicates.resideInAnyPackage(
- "..adapter.inbound.web..",
- "..adapter.outbound.persistence..",
- "..bootstrap..",
- "org.springframework.data.repository..",
- "org.springframework.data.jpa.repository..",
- "jakarta.persistence..",
- "javax.persistence..",
- "org.hibernate..")
- .or(
- JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
- .and(
- JavaClass.Predicates.resideOutsideOfPackage(
- "..adapter.outbound.identifier.."))))
- .as(
- "adapter:outbound:identifier is a non-IO driven adapter (UUIDv7 generation/codec) —"
- + " it must not reach into sibling adapters, persistence, or the composition root"
- + " (feature-resource-identifier-contract §4 taxonomy).")
- .allowEmptyShould(true);
-
- // ---- Task 7: explicit inbound/outbound adapter topology (hexagonal driving/driven split) ----
- // The moves in Tasks 1–6 established the new package topology; the three rules below make it an
- // ENFORCED invariant rather than a convention. The inbound (driving) and outbound (driven)
- // adapter families must never reach across to each other — every interaction between them is
- // mediated by an application port — and the outbound concerns are peers that share code only
- // through :adapter:outbound:support. These generalise the per-adapter isolation rules above
- // (WEB_..., PERSISTENCE_..., OUTBOUND_..., IDENTIFIER_...) into the two family-level directions.
-
- @ArchTest
- static final ArchRule INBOUND_ADAPTERS_DO_NOT_DEPEND_ON_OUTBOUND_ADAPTERS =
- noClasses()
- .that()
- .resideInAPackage("..adapter.inbound..")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("..adapter.outbound..")
- .as(
- "inbound (driving) adapters must not depend on outbound (driven) adapters — an "
- + "inbound adapter drives the application through its inbound ports and reaches "
- + "persistence / messaging / cache / http only via application outbound ports, "
- + "never by touching an outbound adapter directly (Task 7 adapter topology)");
-
- @ArchTest
- static final ArchRule OUTBOUND_ADAPTERS_DO_NOT_DEPEND_ON_INBOUND_ADAPTERS =
- noClasses()
- .that()
- .resideInAPackage("..adapter.outbound..")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("..adapter.inbound..")
- .as(
- "outbound (driven) adapters must not depend on inbound (driving) adapters — a driven "
- + "adapter implements an application outbound port and is oblivious to which "
- + "inbound adapter (web today, a future messaging-inbound tomorrow) triggered "
- + "the call (Task 7 adapter topology)");
-
- @ArchTest
- static final ArchRule OUTBOUND_ADAPTERS_ARE_PEERS_SHARING_ONLY_SUPPORT =
- slices()
- .matching("..adapter.outbound.(*)..")
- .namingSlices("$1")
- .should()
- .notDependOnEachOther()
- // :adapter:outbound:support is the shared base (OutboundCorrelation, fail-open
- // dependency logging, the @Configuration seam) that every outbound adapter is allowed
- // to build on — a dependency whose TARGET resides in support is the sanctioned
- // shared-code path, not peer-to-peer coupling, so it is ignored. Every other
- // cross-slice edge (e.g. messaging -> cache, persistence -> httpclient) is forbidden.
- .ignoreDependency(
- DescribedPredicate.alwaysTrue(),
- JavaClass.Predicates.resideInAPackage("..adapter.outbound.support.."))
- .as(
- "outbound adapters (persistence / identifier / cache / httpclient / messaging / "
- + "notification) are peers: none may depend on a sibling; shared outbound code "
- + "lives in :adapter:outbound:support (Task 7 adapter topology)");
-
- /**
- * A controller is a class Spring routes requests to, wherever it lives.
- *
- *
Selecting by the package string {@code ..adapter.inbound.web..controller..} was selecting a
- * naming convention rather than a role. {@code NotificationCallbackMvcController} sits in {@code
- * ..web.notification.platform.callback}, so the rules that forbid a controller from touching a
- * repository or returning an entity never looked at it — the boundary was unguarded for exactly
- * the endpoint an external provider posts to.
- */
- private static final DescribedPredicate ARE_CONTROLLERS =
- new DescribedPredicate<>("annotated as a Spring controller, or filed as one") {
- @Override
- public boolean test(JavaClass candidate) {
- return candidate.isAnnotatedWith("org.springframework.web.bind.annotation.RestController")
- || candidate.isAnnotatedWith("org.springframework.stereotype.Controller")
- || candidate.isMetaAnnotatedWith(
- "org.springframework.web.bind.annotation.RestController")
- // The package stays in the predicate as well as the annotation. A class filed under
- // ..controller.. is claiming the role whether or not Spring is currently routing to
- // it, and dropping the package would narrow a rule this change exists to widen.
- || (candidate.getPackageName().contains(".adapter.inbound.web")
- && candidate.getPackageName().contains(".controller"));
- }
- };
-
- /** A persistence entity is one the ORM maps, wherever it lives. */
- private static final DescribedPredicate ARE_PERSISTENCE_ENTITIES =
- new DescribedPredicate<>("annotated as a persistence entity") {
- @Override
- public boolean test(JavaClass candidate) {
- return candidate.isAnnotatedWith("jakarta.persistence.Entity")
- || candidate.isAnnotatedWith("jakarta.persistence.MappedSuperclass")
- || candidate.isAnnotatedWith("jakarta.persistence.Embeddable")
- || candidate.isAnnotatedWith(
- "org.springframework.data.mongodb.core.mapping.Document");
- }
- };
-
- /** A mapper is a translation type, identified by its name rather than its directory. */
- private static final DescribedPredicate ARE_PERSISTENCE_MAPPERS =
- new DescribedPredicate<>("a persistence mapper by name") {
- @Override
- public boolean test(JavaClass candidate) {
- return candidate.getSimpleName().endsWith("Mapper")
- && candidate.getPackageName().contains(".adapter.outbound.persistence");
- }
- };
-
- @ArchTest
- static final ArchRule CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES =
- noClasses()
- .that(ARE_CONTROLLERS)
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage(
- "..repository..",
- "..adapter.outbound.persistence..",
- "..adapter.outbound.persistence..entity..",
- "org.springframework.data.repository..",
- "org.springframework.data.jpa.repository..",
- "jakarta.persistence..",
- "javax.persistence..");
-
- /**
- * A datastore handle is a capability, and only the adapter that owns it may hold one.
- *
- *
The MongoDB guardrail existed as a set of forbidden type names with a test that asserted the
- * set's contents. Nothing compared it to a class, so "a controller must not hold a MongoTemplate"
- * was a passing test about a {@code Set} while every controller was governed by nothing —
- * and Boot's own auto-configuration supplies a {@code MongoTemplate}, a {@code MongoClient} and a
- * {@code MongoDatabase} as beans, so the injection was one constructor parameter away.
- *
- *
Checked here, at the composition root, because this is the only place where every production
- * class is on one classpath: a rule that runs inside the Mongo leaf can only see the leaf, and
- * the types it exists to keep out live everywhere else.
- *
- *
A driver handle bypasses the whole platform at once — collection profile, consistency
- * profile, timeout budget, failure translation, observation — which is why the ban covers the
- * driver packages rather than a list of class names that a new driver release can extend.
- */
- @ArchTest
- static final ArchRule ONLY_THE_PERSISTENCE_ADAPTER_HOLDS_A_MONGO_HANDLE =
- noClasses()
- .that()
- .resideInAnyPackage(
- "dev.caskeleton.domain..",
- "dev.caskeleton.application..",
- "dev.caskeleton.adapter.inbound..",
- "dev.caskeleton.bootstrap..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("com.mongodb..", "org.springframework.data.mongodb..")
- .as(
- "only the MongoDB outbound adapter holds a MongoDB template, client, database or "
- + "collection; every other layer reaches MongoDB through an application "
- + "outbound port");
-
- /**
- * Which notification platform package may depend on which.
- *
- *
The design maps 31 logical modules onto packages inside five Gradle leaves, and the registry
- * gate stops at the leaf boundary — so inside a leaf any package could reach any other and the
- * layering existed only in the document. This table is the layering, and it is derived from the
- * edges the code actually has: adding one means adding it here, in review, rather than
- * discovering later that the module graph in the design and the import graph in the source
- * disagree.
- *
- *
{@code api} depends on nothing, which is what makes it usable as the contract everything
- * else shares.
- */
- private static final Map> NOTIFICATION_PLATFORM_PACKAGE_EDGES =
- Map.ofEntries(
- Map.entry("api", Set.of()),
- // port -> template: the template publication port speaks the platform's own template
- // vocabulary — a slot, a published version — rather than restating it as strings. The
- // edge
- // is one-way; template depends on api alone, so nothing cycles through it.
- Map.entry("port", Set.of("api", "template")),
- // admin -> callback/dispatch/observation: the operator plane moved here from the outbound
- // adapter, where operator authorization and transaction orchestration did not belong.
- Map.entry(
- "admin", Set.of("api", "callback", "dispatch", "observation", "policy", "provider")),
- // callback -> port is an implementation depending on the interface it implements, which
- // is the direction that makes the port a boundary. The reverse edge is what was removed.
- Map.entry("callback", Set.of("api", "observation", "port")),
- // dispatch -> port: the split inbound use cases live beside the service they wrap and
- // implement the port interfaces callers depend on.
- Map.entry(
- "dispatch",
- Set.of(
- "api",
- "port",
- "callback",
- "contact",
- "observation",
- "policy",
- "provider",
- "push",
- "security",
- "template")),
- Map.entry("contact", Set.of("api")),
- Map.entry("email", Set.of("api")),
- Map.entry("inbox", Set.of("api")),
- Map.entry("observation", Set.of("api")),
- Map.entry("policy", Set.of("api", "contact", "provider")),
- Map.entry("provider", Set.of("api", "callback", "security", "template")),
- Map.entry("push", Set.of("api")),
- Map.entry("security", Set.of("api", "contact")),
- Map.entry("sms", Set.of("api", "contact")),
- Map.entry("template", Set.of("api")),
- Map.entry("webpush", Set.of("api")));
-
- private static final String NOTIFICATION_PLATFORM_ROOT =
- "dev.caskeleton.application.notification.platform";
-
- @ArchTest
- static final ArchRule NOTIFICATION_PLATFORM_PACKAGE_EDGES_ARE_REGISTERED =
- classes()
- .that()
- .resideInAPackage(NOTIFICATION_PLATFORM_ROOT + "..")
- .should(dependOnlyOnRegisteredNotificationPackages())
- .as(
- "a notification platform package may depend only on the packages registered for it; "
- + "the leaf-level registry cannot see inside a Gradle project, so the module "
- + "graph is enforced here or nowhere (NTF-022)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NOTIFICATION_PLATFORM_PACKAGES_ARE_ACYCLIC =
- slices()
- .matching(NOTIFICATION_PLATFORM_ROOT + ".(*)..")
- .namingSlices("$1")
- .should()
- .beFreeOfCycles()
- .as(
- "a cycle between two notification packages means neither can be understood, "
- + "tested, or extracted without the other (NTF-022)");
-
- @ArchTest
- static final ArchRule NOTIFICATION_ADAPTER_PACKAGES_ARE_ACYCLIC =
- slices()
- .matching("dev.caskeleton.adapter.outbound.notification.platform.(*)..")
- .namingSlices("$1")
- .should()
- .beFreeOfCycles()
- .as("the outbound notification adapter's packages form a DAG too (NTF-022)");
-
- /**
- * Hold every notification platform package to its registered edge set.
- *
- * @return a condition that reports each unregistered package edge
- */
- private static ArchCondition dependOnlyOnRegisteredNotificationPackages() {
- return new ArchCondition<>("depend only on registered notification platform packages") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- String source = notificationPackageOf(item.getPackageName());
- if (source == null) {
- return;
- }
- Set allowed = NOTIFICATION_PLATFORM_PACKAGE_EDGES.get(source);
- if (allowed == null) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "notification platform package '"
- + source
- + "' is not registered in NOTIFICATION_PLATFORM_PACKAGE_EDGES: "
- + item.getName()));
- return;
- }
- for (JavaClass target :
- item.getDirectDependenciesFromSelf().stream()
- .map(dependency -> dependency.getTargetClass())
- .toList()) {
- String edge = notificationPackageOf(target.getPackageName());
- if (edge == null || edge.equals(source) || allowed.contains(edge)) {
- continue;
- }
- events.add(
- SimpleConditionEvent.violated(
- item,
- "unregistered notification platform package edge "
- + source
- + " -> "
- + edge
- + " ("
- + item.getName()
- + " -> "
- + target.getName()
- + ")"));
- }
- }
- };
- }
-
- /**
- * The notification platform sub-package a class belongs to.
- *
- * @param packageName the class's package
- * @return the first segment below the platform root, or null when the class is outside it
- */
- private static String notificationPackageOf(String packageName) {
- if (!packageName.startsWith(NOTIFICATION_PLATFORM_ROOT + ".")) {
- return null;
- }
- String remainder = packageName.substring(NOTIFICATION_PLATFORM_ROOT.length() + 1);
- int dot = remainder.indexOf('.');
- return dot < 0 ? remainder : remainder.substring(0, dot);
- }
-
- @ArchTest
- static final ArchRule PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER =
- classes()
- .that(ARE_PERSISTENCE_ENTITIES)
- .should()
- .resideInAnyPackage(
- "..adapter.outbound.persistence..",
- "..sample.portfolio.adapter.outbound.persistence..")
- .as(
- "a type the ORM maps belongs to a persistence adapter whatever it is called and "
- + "wherever it is filed (NTF-022 semantic selection)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule WEB_DTOS_STAY_IN_WEB_ADAPTER =
- classes()
- .that()
- .resideInAPackage("..adapter.inbound.web..dto..")
- .should()
- .onlyBeAccessed()
- .byClassesThat()
- .resideInAnyPackage("..adapter.inbound.web..")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES =
- noClasses()
- .that(ARE_PERSISTENCE_MAPPERS)
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..")
- .allowEmptyShould(true);
-
- @ArchTest
- // KNOWN DEFECT, deliberately left as found. Two things are wrong and they mask each other: the
- // subject pattern "..shared.." matches any package named shared anywhere rather than this module,
- // and the allowlist below holds the bare module root while the check matches by prefix — so every
- // package inside dev.caskeleton.shared is blessed automatically. The rule therefore catches
- // nothing in the module it is named after, and does catch unrelated leaves that happen to have a
- // "shared" package. Fixing it needs the violation fixture to move inside dev.caskeleton.shared,
- // which puts it in this rule's own analysis scope, which needs the fixture-scanning structure
- // reworked. That is a separate change; see the Wave 1 progress note.
- static final ArchRule SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES =
- classes()
- .that()
- .resideInAPackage("..shared..")
- .should(resideInAllowedSharedContractPackage())
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO =
- noClasses()
- .that()
- .resideOutsideOfPackage("..sample.portfolio..")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("..sample.portfolio..");
-
- @ArchTest
- static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .and()
- .resideOutsideOfPackage("..fixtures..")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("..fixtures..")
- .as(
- "feature-test-taxonomy-fixture-contract D6: production code must never depend on a"
- + " test fixture — fixtures live in src/test/.../fixtures/ (test-only); this"
- + " guards against a fixture leaking onto the main classpath.")
- .allowEmptyShould(true);
-
- // ---- feature-boundary-validation-mapping-contract ----
-
- @ArchTest
- static final ArchRule REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS =
- classes()
- .that()
- .resideInAPackage("..adapter.inbound.web..dto..")
- .should(notBeAnnotatedWithJsonIgnoreUnknown())
- .as(
- "class-level @JsonIgnoreProperties(ignoreUnknown = true) on a request DTO "
- + "silently swallows extra fields and defeats the B1 fail-fast boundary "
- + "(feature-boundary-validation-mapping-contract B1)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName(
- "com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator")
- .as(
- "LaissezFaireSubTypeValidator allows any subtype during polymorphic "
- + "deserialization and is the CVE-2019-14379 RCE entry point "
- + "(feature-boundary-validation-mapping-contract B5)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .callMethodWhere(target(name("enableDefaultTyping")))
- .as(
- "ObjectMapper.enableDefaultTyping() (deprecated since Jackson 2.10) opens "
- + "polymorphic deserialization to all classes — the documented "
- + "CVE-2019-14379 RCE vector (feature-boundary-validation-mapping-contract B5). "
- + "Use @JsonTypeInfo(use = NAME) + @JsonSubTypes, or "
- + "activateDefaultTyping(BasicPolymorphicTypeValidator) with an explicit "
- + "allowlist — the LaissezFaireSubTypeValidator class reference rule "
- + "still blocks the unsafe variant.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_INHERITABLE_THREAD_LOCAL =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName("java.lang.InheritableThreadLocal")
- .as(
- "InheritableThreadLocal does not propagate cleanly to virtual threads "
- + "and leaks context across pooled carrier threads — use MDC (SLF4J "
- + "2.0+) or RequestContextHolder instead "
- + "(feature-boundary-validation-mapping-contract B6)")
- .allowEmptyShould(true);
-
- // ---- feature-runtime-context-propagation-contract (domain extension of B6) ----
- // Strategy guard for the domain-context propagation seam. The shipped default is
- // ThreadLocal (DomainContextStrategy.THREAD_LOCAL — plain ThreadLocal, no preview
- // API); the MICROMETER / SCOPED_VALUE strategies are reserved-but-unshipped. This
- // rule keeps the two Java 21 PREVIEW primitives (ScopedValue / StructuredTaskScope)
- // out of production code until the SCOPED_VALUE strategy is actually activated
- // through the factory seam. The build has no --enable-preview (build fact C1), so
- // those APIs cannot even compile today — the compile gate is the first line of
- // defence and is why a compilable violation fixture is infeasible; this rule is the
- // dormant defence-in-depth that fires the moment --enable-preview is enabled and
- // someone reaches for the preview primitive without adding the strategy + factory
- // case. It is NOT the deferred S4/D7 activation-correctness rule
- // (UNSUPPORTED_IMPL_DECISION — rule shape intentionally not invented). The
- // implicit-inheritance ban (S3, the ThreadLocal default's contract) cross-cites
- // NO_INHERITABLE_THREAD_LOCAL above (owner: boundary-validation B6).
-
- @ArchTest
- static final ArchRule DOMAIN_CONTEXT_PROPAGATION_PRIMITIVES_STAY_UNSHIPPED =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .haveNameMatching(
- "(java\\.lang\\.ScopedValue"
- + "|java\\.util\\.concurrent\\.StructuredTaskScope)(\\$.*)?")
- .as(
- "the domain-context default is ThreadLocal; the ScopedValue / "
- + "StructuredTaskScope (preview) strategy must not appear in production "
- + "code until activated through the DomainContextStrategy + factory seam "
- + "(feature-runtime-context-propagation-contract S2)")
- .allowEmptyShould(true);
-
- // ---- feature-boundary-validation-mapping-contract — 판정 기준 §Forbidden + 테스트 계약 ----
-
- @ArchTest
- static final ArchRule CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES =
- methods()
- .that()
- .areDeclaredInClassesThat(ARE_CONTROLLERS)
- .and()
- .arePublic()
- .should()
- .notHaveRawReturnType(
- JavaClass.Predicates.resideInAnyPackage(
- "..domain..",
- "..adapter.outbound.persistence.entity..",
- "..adapter.outbound.persistence.repository.."))
- .as(
- "controller methods must return DTO / record / Envelope / ResponseEntity — "
- + "never a domain entity, JPA entity, or repository type "
- + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS =
- methods()
- .that()
- .areDeclaredInClassesThat()
- .resideInAPackage("..application..")
- .and()
- .arePublic()
- .should()
- .notHaveRawParameterTypes(
- DescribedPredicate.describe(
- "any in ..adapter.inbound.web..dto..",
- (List params) -> {
- for (JavaClass p : params) {
- if (JavaClass.Predicates.resideInAPackage("..adapter.inbound.web..dto..")
- .test(p)) {
- return true;
- }
- }
- return false;
- }))
- .as(
- "application methods must not accept request/response DTOs — "
- + "controllers must translate to Command/Query before invoking the use case "
- + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_PROBLEM_DETAIL_USAGE =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName("org.springframework.http.ProblemDetail")
- .as(
- "D5: RFC 7807 ProblemDetail is explicitly rejected — error responses must use "
- + "the shared Envelope shape "
- + "(feature-boundary-validation-mapping-contract D5). Production code is "
- + "scanned only — test fixtures in ..architecture.violations.. are out of "
- + "scope under ImportOption.DoNotIncludeTests.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING =
- classes()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should(notReferenceMediaTypeString("application/merge-patch+json"))
- .as(
- "B2: RFC 7396 application/merge-patch+json content type is explicitly rejected "
- + "— PATCH endpoints must use JSON with JsonNullable 3-state "
- + "(feature-boundary-validation-mapping-contract B2)")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES =
- methods()
- .that()
- .areDeclaredInClassesThat(
- JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
- .and(implementsApplicationLayerPort()))
- // @Configuration factory classes are excluded: a @Bean method legitimately
- // returns the adapter's own outbound port type for DI wiring (e.g.
- // MessagingConfig#messagePublisher -> MessagePublisher) — that is
- // composition-root assembly, not an adapter *response* method leaking an
- // external type. B7 targets the latter (ACL leakage), not DI factories
- // (feature-integration-adapter-templates Layer 1 @ConditionalOnProperty configs).
- .and()
- .areDeclaredInClassesThat()
- .areNotAnnotatedWith("org.springframework.context.annotation.Configuration")
- // @ConfigurationProperties settings holders are excluded for the same reason as
- // @Configuration factories: they are config-binding types, not adapter *response*
- // surfaces. Their accessors return bound config values (primitives, Duration,
- // DataSize) and nested config records (e.g. OutboundHttpSettings#retry ->
- // OutboundHttpSettings.Retry) — internal settings, not external HTTP / messaging
- // responses, so B7's ACL-leakage target does not apply.
- .and()
- .areDeclaredInClassesThat()
- .areNotAnnotatedWith(
- "org.springframework.boot.context.properties.ConfigurationProperties")
- // The canonical HTTP control plane and explicit legacy factories have a small exact
- // method whitelist below. Names, packages, and arbitrary @Bean annotations do not bypass
- // B7.
- .and(notAnExactHttpControlPlaneMethod())
- // Redis provider binding and secret material provider SPIs likewise expose only a small
- // exact set of typed composition accessors. Command/router/native runtime and secret
- // value accessors are deliberately not exempt.
- .and(notAnExactRedisCompositionMethod())
- .and()
- // Package-private implementation types cannot expose their methods outside the
- // adapter package. B7 protects the externally reachable adapter API, not internal
- // records and fault-injection seams used to implement that API.
- .areDeclaredInClassesThat()
- .arePublic()
- .and()
- .arePublic()
- .and()
- .areNotStatic()
- .should()
- .notHaveRawReturnType(
- JavaClass.Predicates.resideInAnyPackage(
- "..adapter.outbound..",
- "..adapter.inbound.web..",
- "..adapter.outbound.persistence.."))
- .as(
- "B7: outbound application-port adapter public methods must return domain types"
- + " (or primitives/wrappers/Optional) — raw external response types must not"
- + " escape the adapter package (feature-boundary-validation-mapping-contract B7"
- + " ACL). @Configuration @Bean factory methods and @ConfigurationProperties"
- + " settings holders are excluded — they assemble port bindings / bind config,"
- + " not adapter response surfaces.")
- .allowEmptyShould(true);
-
- private static final Set B7_EXACT_HTTP_CONTROL_PLANE_METHODS =
- // The HTTP Client Platform (feature-httpclient-platform) exposes no outbound adapter
- // class that implements an application-layer port, so B7 has no HTTP control-plane
- // method to exempt. The whitelist mechanism stays in place so a future application-port
- // implementation must justify each exact accessor rather than widen the rule.
- Set.of();
-
- private static DescribedPredicate notAnExactHttpControlPlaneMethod() {
- return new DescribedPredicate<>("not an exact HTTP control-plane accessor/factory") {
- @Override
- public boolean test(JavaMethod method) {
- return !B7_EXACT_HTTP_CONTROL_PLANE_METHODS.contains(ExactMethodSignature.from(method));
- }
- };
- }
-
- private static final Set B7_EXACT_REDIS_COMPOSITION_METHODS =
- Set.of(
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings",
- "dataAuthentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings",
- "dataTls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone",
- "dataAuthentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone",
- "dataTls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
- "sentinelAuthentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
- "sentinelTls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
- "dataAuthentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
- "dataTls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster",
- "dataAuthentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster",
- "dataTls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
- "topology",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$Topology"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
- "standalone",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$StandaloneProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
- "sentinel",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
- "cluster",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$ClusterProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
- "authentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
- "tls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties",
- "authentication",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties",
- "tls",
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$RuntimeProperties",
- "clientSettings",
- "dev.caskeleton.adapter.outbound.cache.redis.runtime.RedisClientRuntimeSettings"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider",
- "resolve",
- List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"),
- "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial"),
- signature(
- "dev.caskeleton.adapter.outbound.cache.redis.security.RedisTrustMaterialProvider",
- "resolve",
- List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"),
- "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisTrustMaterial"));
-
- private static DescribedPredicate notAnExactRedisCompositionMethod() {
- return new DescribedPredicate<>("not an exact Redis composition accessor or material SPI") {
- @Override
- public boolean test(JavaMethod method) {
- return !B7_EXACT_REDIS_COMPOSITION_METHODS.contains(ExactMethodSignature.from(method));
- }
- };
- }
-
- private static ExactMethodSignature signature(String owner, String name, String returnType) {
- return signature(owner, name, List.of(), returnType);
- }
-
- private static ExactMethodSignature signature(
- String owner, String name, List parameters, String returnType) {
- return new ExactMethodSignature(owner, name, parameters, returnType);
- }
-
- private record ExactMethodSignature(
- String owner, String name, List parameters, String returnType) {
-
- private ExactMethodSignature {
- parameters = List.copyOf(parameters);
- }
-
- private static ExactMethodSignature from(JavaMethod method) {
- List parameterTypes =
- method.getRawParameterTypes().stream().map(JavaClass::getFullName).toList();
- return new ExactMethodSignature(
- method.getOwner().getFullName(),
- method.getName(),
- parameterTypes,
- method.getRawReturnType().getFullName());
- }
- }
-
- @ArchTest
- static final ArchRule
- MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS =
- methods()
- .that()
- .areDeclaredInClassesThat()
- .resideInAPackage("..adapter.outbound.messaging..")
- .and()
- .areDeclaredInClassesThat()
- .areNotAnnotatedWith("org.springframework.context.annotation.Configuration")
- .and()
- .areDeclaredInClassesThat()
- .areNotAnnotatedWith(
- "org.springframework.boot.context.properties.ConfigurationProperties")
- .and()
- .arePublic()
- .and()
- .areNotStatic()
- .should(
- notReturnAdapterTypesIncludingGenericArguments(
- "..adapter.outbound..",
- "..adapter.inbound.web..",
- "..adapter.outbound.persistence.."))
- .as(
- "B7 messaging hardening: every public non-static messaging method must keep "
- + "adapter-local types out of direct and recursive generic return positions; "
- + "narrow public static composition bridges are the only allowed adapter-local "
- + "return seam")
- .allowEmptyShould(true);
-
- private static ArchCondition notReturnAdapterTypesIncludingGenericArguments(
- String... forbiddenPackages) {
- DescribedPredicate forbidden =
- JavaClass.Predicates.resideInAnyPackage(forbiddenPackages);
- return new ArchCondition<>(
- "not return adapter-local types directly or through generic arguments") {
- @Override
- public void check(JavaMethod method, ConditionEvents events) {
- for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) {
- if (forbidden.test(involved)) {
- events.add(
- SimpleConditionEvent.violated(
- method,
- "Method "
- + method.getFullName()
- + " leaks "
- + involved.getName()
- + " through its return type (directly or as a generic argument)"
- + " — B7 permits only narrow static composition bridges for "
- + "adapter-local types"));
- }
- }
- }
- };
- }
-
- private static DescribedPredicate implementsApplicationLayerPort() {
- return DescribedPredicate.describe(
- "implement an application-layer port",
- type ->
- type.getAllRawInterfaces().stream()
- .anyMatch(
- iface ->
- iface.getPackageName().contains(".application.")
- || iface.getPackageName().endsWith(".application")));
- }
-
- @ArchTest
- static final ArchRule VALID_CASCADE_DEPTH_AT_MOST_THREE =
- classes()
- .that()
- .resideInAPackage("..adapter.inbound.web..dto..")
- .should(notHaveValidCascadeDeeperThan(3))
- .as(
- "B4: @Valid cascade depth must be ≤ 3 to bound nested-DTO recursion cost "
- + "(feature-boundary-validation-mapping-contract B4 — DoS defense)")
- .allowEmptyShould(true);
-
- // ---- feature-business-rule-validation-contract C1/D1 (validation stays at the web boundary)
- // ----
-
- @ArchTest
- static final ArchRule VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY =
- noClasses()
- .that()
- .resideInAnyPackage("..domain..", "..application..")
- .should()
- .dependOnClassesThat()
- .resideInAPackage("jakarta.validation..")
- .as(
- "C1/D1: Bean Validation (jakarta.validation @NotNull / @Valid / ...) is the "
- + "syntax layer and must stay at the adapter-web request boundary. Domain "
- + "invariants live in domain model / value-object constructors and application "
- + "policy is plain Java — neither is expressed with annotation-driven "
- + "validation (feature-business-rule-validation-contract C1/D1, boundary B4 "
- + "layering). Production code is scanned only — fixtures in "
- + "..architecture.violations.. are out of scope under "
- + "ImportOption.DoNotIncludeTests.")
- .allowEmptyShould(true);
-
- // ---- feature-api-contract-baseline D19 (resource URL naming, AIP-122) ----
-
- @ArchTest
- static final ArchRule CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122 =
- classes()
- .that()
- .resideInAPackage("..adapter.inbound.web..controller..")
- .should(haveAip122CompliantRequestMappingPaths())
- .as(
- "D19: controller @RequestMapping path segments must follow AIP-122 — lowercase "
- + "first char + ASCII alphanumerics ([a-z][a-zA-Z0-9]*), so kebab-case "
- + "(/work-logs), CamelCase (/Tickets) and SNAKE_CASE are rejected. "
- + "{pathVariables} and AIP-136 colon-verb custom methods "
- + "(/worklogs:batchCreate) are allowed (feature-api-contract-baseline D19)")
- .allowEmptyShould(true);
-
- private static final Pattern AIP122_SEGMENT = Pattern.compile("[a-z][a-zA-Z0-9]*");
- private static final Pattern AIP136_VERB = Pattern.compile("[a-zA-Z][a-zA-Z0-9]*");
- private static final Set MAPPING_ANNOTATION_TYPES =
- Set.of(
- "org.springframework.web.bind.annotation.RequestMapping",
- "org.springframework.web.bind.annotation.GetMapping",
- "org.springframework.web.bind.annotation.PostMapping",
- "org.springframework.web.bind.annotation.PutMapping",
- "org.springframework.web.bind.annotation.PatchMapping",
- "org.springframework.web.bind.annotation.DeleteMapping");
-
- private static ArchCondition haveAip122CompliantRequestMappingPaths() {
- return new ArchCondition<>("have AIP-122 compliant @RequestMapping path segments") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- checkMappingPaths(item, item.getAnnotations(), events);
- for (JavaMethod m : item.getMethods()) {
- checkMappingPaths(item, m.getAnnotations(), events);
- }
- }
- };
- }
-
- private static void checkMappingPaths(
- JavaClass owner, Set extends JavaAnnotation>> annotations, ConditionEvents events) {
- for (JavaAnnotation> annotation : annotations) {
- if (!MAPPING_ANNOTATION_TYPES.contains(annotation.getRawType().getName())) {
- continue;
- }
- for (String key : List.of("value", "path")) {
- annotation
- .get(key)
- .ifPresent(
- value -> {
- for (String path : toPathStrings(value)) {
- for (String segment : path.split("/", 0)) {
- validatePathSegment(owner, segment, path, events);
- }
- }
- });
- }
- }
- }
-
- private static List toPathStrings(Object value) {
- if (value instanceof String s) {
- return List.of(s);
- }
- if (value instanceof Object[] arr) {
- List out = new ArrayList<>();
- for (Object e : arr) {
- if (e != null) {
- out.add(e.toString());
- }
- }
- return out;
- }
- return List.of();
- }
-
- private static void validatePathSegment(
- JavaClass owner, String segment, String path, ConditionEvents events) {
- if (segment.isEmpty() || (segment.startsWith("{") && segment.endsWith("}"))) {
- return; // empty segment or {pathVariable}
- }
- String[] colonParts = segment.split(":", 2);
- String resource = colonParts[0];
- if (!resource.isEmpty() && !AIP122_SEGMENT.matcher(resource).matches()) {
- events.add(
- SimpleConditionEvent.violated(
- owner,
- "Class "
- + owner.getName()
- + " maps non-AIP-122 path segment '"
- + segment
- + "' in '"
- + path
- + "' — use lowercase [a-z][a-zA-Z0-9]* (no kebab/Camel/snake)"));
- return;
- }
- if (colonParts.length == 2 && !AIP136_VERB.matcher(colonParts[1]).matches()) {
- events.add(
- SimpleConditionEvent.violated(
- owner,
- "Class "
- + owner.getName()
- + " maps non-AIP-136 colon-verb '"
- + segment
- + "' in '"
- + path
- + "'"));
- }
- }
-
- private static ArchCondition notReferenceMediaTypeString(String mediaType) {
- return new ArchCondition<>("not reference media type string '" + mediaType + "'") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- // The realistic carriers — @RequestMapping(consumes=...),
- // @PostMapping(consumes=...), and so on — sit on classes AND methods.
- // Bytecode does not retain String initializer values for non-final
- // fields, so free-form references inside method bodies cannot be
- // detected at the ArchUnit level. We catch the annotation carriers
- // (class and method) which is where the realistic usage lives.
- scanAnnotations(item, item.getAnnotations(), mediaType, events);
- for (JavaMethod m : item.getMethods()) {
- scanAnnotations(item, m.getAnnotations(), mediaType, events);
- }
- }
- };
- }
-
- private static void scanAnnotations(
- JavaClass owner,
- Set extends JavaAnnotation>> annotations,
- String mediaType,
- ConditionEvents events) {
- for (JavaAnnotation> annotation : annotations) {
- // Annotation properties on Spring's @RequestMapping family carry their values as
- // String[] which boxes to Object[]. Some ArchUnit versions also expose property
- // values as plain String. Walk both shapes and also fall back to the rendered
- // annotation text so single-value carriers (e.g. MediaType constants resolved at
- // bytecode level) are caught.
- for (Object v : annotation.getProperties().values()) {
- if (matches(v, mediaType)) {
- events.add(
- SimpleConditionEvent.violated(
- owner,
- "Class "
- + owner.getName()
- + " uses '"
- + mediaType
- + "' via @"
- + annotation.getRawType().getSimpleName()));
- return;
- }
- }
- if (annotation.toString().contains(mediaType)) {
- events.add(
- SimpleConditionEvent.violated(
- owner,
- "Class "
- + owner.getName()
- + " uses '"
- + mediaType
- + "' (detected in @"
- + annotation.getRawType().getSimpleName()
- + " text)"));
- }
- }
- }
-
- private static boolean matches(Object value, String mediaType) {
- if (value == null) {
- return false;
- }
- if (value instanceof String s) {
- return s.equals(mediaType);
- }
- if (value instanceof Object[] arr) {
- for (Object e : arr) {
- if (matches(e, mediaType)) {
- return true;
- }
- }
- }
- if (value instanceof Iterable> iter) {
- for (Object e : iter) {
- if (matches(e, mediaType)) {
- return true;
- }
- }
- }
- // Fallback: rendered representation. Catches Spring annotation values that
- // get wrapped in opaque ArchUnit holders whose toString carries the literal.
- return value.toString().contains(mediaType);
- }
-
- private static ArchCondition notHaveValidCascadeDeeperThan(int maxDepth) {
- return new ArchCondition<>("not have @Valid cascade deeper than " + maxDepth + " nested DTOs") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- int depth = computeValidDepth(item, new HashSet<>(), 0);
- if (depth > maxDepth) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " has @Valid cascade depth "
- + depth
- + " (max allowed: "
- + maxDepth
- + ")"));
- }
- }
-
- private int computeValidDepth(JavaClass type, Set visited, int currentDepth) {
- if (!visited.add(type.getName())) {
- return currentDepth;
- }
- int maxFound = currentDepth;
- for (JavaField field : type.getFields()) {
- boolean hasValid = false;
- for (JavaAnnotation> a : field.getAnnotations()) {
- if (a.getRawType().getName().equals("jakarta.validation.Valid")) {
- hasValid = true;
- break;
- }
- }
- if (hasValid) {
- JavaClass fieldType = field.getRawType();
- if (fieldType.getPackageName().startsWith("dev.caskeleton")) {
- int sub = computeValidDepth(fieldType, visited, currentDepth + 1);
- if (sub > maxFound) {
- maxFound = sub;
- }
- }
- }
- }
- return maxFound;
- }
- };
- }
-
- private static ArchCondition notBeAnnotatedWithJsonIgnoreUnknown() {
- return new ArchCondition<>(
- "not be annotated with @JsonIgnoreProperties(ignoreUnknown = true)") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- item.tryGetAnnotationOfType("com.fasterxml.jackson.annotation.JsonIgnoreProperties")
- .ifPresent(
- annotation -> {
- Object ignoreUnknown = annotation.get("ignoreUnknown").orElse(null);
- if (Boolean.TRUE.equals(ignoreUnknown)) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " is annotated with "
- + "@JsonIgnoreProperties(ignoreUnknown = true) — "
- + "use field-level @JsonIgnore on the specific "
- + "field instead (B1)"));
- }
- });
- }
- };
- }
-
- // ---- feature-resource-identifier-contract D17 (decision SSOT: resource-identifier branch) ----
-
- @ArchTest
- static final ArchRule NO_LONG_ID_PK =
- fields()
- .that()
- .areDeclaredInClassesThat()
- .resideInAPackage("..domain..")
- .and()
- .haveNameMatching("id")
- .should()
- .haveRawType(assignableTo(ResourceId.class))
- .as(
- "D17 NO_LONG_ID_PK: a domain entity 'id' field must be a ResourceId value object"
- + " (e.g. WorkLogId), never Long/long/int/Integer"
- + " (feature-resource-identifier-contract D17). JPA @Id UUID columns in"
- + " ..adapter.outbound.persistence.. are out of scope — they store the UUID as"
- + " the PostgreSQL native uuid type per D10.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_UUID_RANDOM_IN_CONTROLLER =
- noClasses()
- .that()
- .resideInAnyPackage("..adapter.inbound.web..controller..", "..application..")
- .should()
- .callMethod(UUID.class, "randomUUID")
- .orShould()
- .dependOnClassesThat()
- .haveFullyQualifiedName("com.github.f4b6a3.uuid.UuidCreator")
- .as(
- "D5/D17 NO_UUID_RANDOM_IN_CONTROLLER: controllers and the application layer "
- + "(use cases / services) must not generate resource identifiers directly "
- + "(UUID.randomUUID / UuidCreator) — inject the domain WorkLogIdFactory port "
- + "instead (feature-resource-identifier-contract D5/D17). Scope follows the "
- + "D17 decision text (controller / service / use case); request/trace-id "
- + "generation in web filters is a distinct concern owned by D18 / "
- + "feature-distributed-tracing-contract and is intentionally out of scope.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_MATH_RANDOM_FOR_ID =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .callMethod(Math.class, "random")
- .as(
- "D9/D17 NO_MATH_RANDOM_FOR_ID: Math.random() is not a CSPRNG and must never "
- + "seed an identifier (feature-resource-identifier-contract D9/D17).")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_VARCHAR_255_FOR_ID_COLUMN =
- fields()
- .that()
- .areAnnotatedWith("jakarta.persistence.Column")
- .and()
- .haveNameMatching(".*[iI]d")
- .should(haveExplicitIdColumnMapping())
- .as(
- "D17 NO_VARCHAR_255_FOR_ID_COLUMN: an @Column-mapped id field must declare an "
- + "explicit non-default length or a portable UUID type hint — never fall "
- + "back to varchar(255) (feature-resource-identifier-contract D17).")
- .allowEmptyShould(true);
-
- // ---- feature-streaming-response-contract D3 (event/server-push streaming ban) ----
-
- @ArchTest
- static final ArchRule NO_SSE_EMITTER =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName(
- "org.springframework.web.servlet.mvc.method.annotation.SseEmitter")
- .as(
- "D3 NO_SSE_EMITTER: production code must not import SseEmitter — "
- + "SSE server-push streaming (W3C EventSource / text/event-stream) is "
- + "not supported in ca-skeleton (feature-streaming-response-contract D3). "
- + "Production code is scanned only — test fixtures in "
- + "..architecture.violations.. are out of scope under "
- + "ImportOption.DoNotIncludeTests.")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_RESPONSE_BODY_EMITTER =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .haveFullyQualifiedName(
- "org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter")
- .as(
- "D3 NO_RESPONSE_BODY_EMITTER: production code must not import "
- + "ResponseBodyEmitter — it is the SSE base class and incremental "
- + "object-emit surface (SPRING-ASYNC-C3) treated as server-push "
- + "(UNSUPPORTED_IMPL_DECISION④). Note: StreamingResponseBody "
- + "(large-file download, request-response model) is NOT blocked — "
- + "it is owned by feature-file-resource-handling-contract D8 "
- + "(feature-streaming-response-contract D3).")
- .allowEmptyShould(true);
-
- @ArchTest
- static final ArchRule NO_WEBSOCKET_HANDLER =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .dependOnClassesThat()
- .resideInAnyPackage("org.springframework.web.socket..", "jakarta.websocket..")
- .as(
- "D3 NO_WEBSOCKET_HANDLER: production code must not depend on "
- + "org.springframework.web.socket.. or jakarta.websocket.. — "
- + "these cover the spring-websocket handler/STOMP surfaces and "
- + "Jakarta @ServerEndpoint (RFC 6455 full-duplex WebSocket model). "
- + "Full-duplex streaming is not supported in ca-skeleton "
- + "(feature-streaming-response-contract D3). Production code is "
- + "scanned only under ImportOption.DoNotIncludeTests.")
- .allowEmptyShould(true);
-
- // ---- feature-secrets-config-source-contract D3/D10 (no-runtime-reload static enforcement) ----
-
- @ArchTest
- static final ArchRule NO_REFRESH_SCOPE_ANYWHERE =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .beAnnotatedWith("org.springframework.cloud.context.config.annotation.RefreshScope")
- .as(
- "D3/D10 NO_REFRESH_SCOPE_ANYWHERE: secrets default to no-runtime-reload "
- + "(rotation = restart validation), so @RefreshScope is banned across the "
- + "whole production tree — it is the in-process config-reload machinery the "
- + "VAULT-DYN-C2 dynamic-reload rejection forbids "
- + "(feature-secrets-config-source-contract D3/D10). The ban is total (no "
- + "carve-out): an explicit rotation handler (e.g. JWT 24h overlap, owned by "
- + "another branch) rotates via a mutable holder + scheduled swap, never "
- + "@RefreshScope. spring-cloud-context is not on the production classpath, so "
- + "the annotation is referenced by fully-qualified name. Production code is "
- + "scanned only — the fixture in ..architecture.violations.secrets.. is out "
- + "of scope under ImportOption.DoNotIncludeTests.")
- .allowEmptyShould(true);
-
- // ---- feature-schema-serialization-contract D3 (BigDecimal precision trap ban) ----
-
- @ArchTest
- static final ArchRule NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR =
- noClasses()
- .that()
- .resideInAPackage("dev.caskeleton..")
- .should()
- .callConstructor(BigDecimal.class, double.class)
- .orShould()
- .callConstructor(BigDecimal.class, float.class)
- .as(
- "D3 NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR: new BigDecimal(double) / "
- + "new BigDecimal(float) captures the binary floating-point error "
- + "(new BigDecimal(0.1) == 0.1000000000000000055...) instead of the exact "
- + "decimal — money/decimal values must be built from a String "
- + "(new BigDecimal(\"0.1\") or BigDecimal.valueOf(double)) "
- + "(feature-schema-serialization-contract D3 / SBMS-C3). Production code is "
- + "scanned only — test fixtures in ..architecture.violations.. are out of "
- + "scope under ImportOption.DoNotIncludeTests.")
- .allowEmptyShould(true);
-
- // ---- feature-application-query-bypass-contract D1 (read/query port purity guardrail) ----
-
- @ArchTest
- static final ArchRule QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES =
- methods()
- .that()
- .areDeclaredInClassesThat(
- JavaClass.Predicates.resideInAPackage("..application..")
- .and(JavaClass.Predicates.simpleNameEndingWith("QueryPort")))
- .and()
- .arePublic()
- .should(
- notLeakDomainJpaOrWebThroughReturnType(
- "..domain..",
- "..adapter..",
- "jakarta.persistence..",
- "javax.persistence..",
- "org.springframework.web..",
- "org.hibernate.."))
- .as(
- "D1 QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES: application read/query ports"
- + " (classes whose simple name ends with 'QueryPort') must return"
- + " application-layer projection DTOs — never a domain aggregate, JPA entity, or"
- + " web type, INCLUDING through generic type arguments like List"
- + " (checked via JavaType.getAllInvolvedRawTypes(), since a raw-return-type check"
- + " alone misses generic leakage) (feature-application-query-bypass-contract D1"
- + " core purity guardrail). The through-aggregate read path (repository ports"
- + " returning the domain aggregate) is a separate, equally-valid choice and is"
- + " intentionally out of this rule's scope.")
- .allowEmptyShould(true);
-
- private static ArchCondition notLeakDomainJpaOrWebThroughReturnType(
- String... forbiddenPackages) {
- DescribedPredicate forbidden =
- JavaClass.Predicates.resideInAnyPackage(forbiddenPackages);
- return new ArchCondition<>(
- "return application projection DTOs only "
- + "(no domain / adapter / JPA / web types, including generic type arguments)") {
- @Override
- public void check(JavaMethod method, ConditionEvents events) {
- // getAllInvolvedRawTypes() flattens the return type plus every generic type
- // argument (recursively) to their raw classes — so List surfaces
- // both java.util.List AND DomainType, which a raw-return-type predicate misses.
- for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) {
- if (forbidden.test(involved)) {
- events.add(
- SimpleConditionEvent.violated(
- method,
- "Method "
- + method.getFullName()
- + " leaks "
- + involved.getName()
- + " through its return type (directly or as a generic type argument)"
- + " — a *QueryPort must return application-layer projection DTOs "
- + "(feature-application-query-bypass-contract D1)"));
- }
- }
- }
- };
- }
-
- // ---- feature-domain-modeling-guardrails custom conditions ----
-
- private static final Set AUDIT_FIELD_NAMES =
- Set.of("createdat", "updatedat", "createdby", "updatedby");
-
- private static ArchCondition declareAnAuditField() {
- return new ArchCondition<>(
- "declare an audit field " + "(createdAt / updatedAt / createdBy / updatedBy)") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- for (JavaField field : item.getFields()) {
- if (AUDIT_FIELD_NAMES.contains(field.getName().toLowerCase(Locale.ROOT))) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " declares audit field '"
- + field.getName()
- + "' — audit metadata must live only on the adapter-persistence "
- + "AuditableEntity @MappedSuperclass, not on a domain class "
- + "(feature-persistence-auditing-contract D2)"));
- }
- }
- }
- };
- }
-
- private static ArchCondition notHaveAPublicNoArgConstructor() {
- return new ArchCondition<>("not have a public no-arg constructor") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- for (JavaConstructor constructor : item.getConstructors()) {
- if (constructor.getRawParameterTypes().isEmpty()
- && constructor.getModifiers().contains(JavaModifier.PUBLIC)) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " exposes a public no-arg constructor — "
- + "a value object must validate its invariant on the sole "
- + "construction path and offer no empty back door "
- + "(feature-domain-modeling-guardrails D5/D6)"));
- }
- }
- }
- };
- }
-
- private static ArchCondition beRecordTypes() {
- return new ArchCondition<>("be a record") {
- @Override
- public void check(JavaClass item, ConditionEvents events) {
- if (!item.isRecord()) {
- events.add(
- SimpleConditionEvent.violated(
- item,
- "Class "
- + item.getName()
- + " is annotated @DomainEvent but is not a "
- + "record — a domain event is an immutable fact and must be a "
- + "record (feature-domain-modeling-guardrails D4/D8)"));
- }
- }
- };
- }
-
- private static ArchCondition notDeclareColumnDefinition() {
- return new ArchCondition<>("not declare @Column columnDefinition") {
- @Override
- public void check(JavaField field, ConditionEvents events) {
- field
- .tryGetAnnotationOfType("jakarta.persistence.Column")
- .ifPresent(
- column -> {
- String columnDefinition =
- column.get("columnDefinition").map(Object::toString).orElse("");
- if (!columnDefinition.isBlank()) {
- events.add(
- SimpleConditionEvent.violated(
- field,
- "Field "
- + field.getFullName()
- + " pins vendor SQL columnDefinition='"
- + columnDefinition
- + "' in adapter:outbound:persistence-jpa; move the "
- + "physical type to the vendor migration."));
- }
- });
- }
- };
- }
-
- private static ArchCondition haveExplicitIdColumnMapping() {
- return new ArchCondition<>(
- "declare an explicit non-default length or portable UUID type hint") {
- @Override
- public void check(JavaField field, ConditionEvents events) {
- field
- .tryGetAnnotationOfType("jakarta.persistence.Column")
- .ifPresent(
- column -> {
- int length = column.get("length").map(v -> ((Number) v).intValue()).orElse(255);
- boolean explicit = length != 255 || hasPortableUuidTypeHint(field);
- if (!explicit) {
- events.add(
- SimpleConditionEvent.violated(
- field,
- "Field "
- + field.getFullName()
- + " maps an id column with the implicit "
- + "varchar(255) default — declare an explicit length or "
- + "@JdbcTypeCode(SqlTypes.UUID) for UUID fields "
- + "(feature-resource-identifier-contract D17)"));
- }
- });
- }
- };
- }
-
- private static boolean hasPortableUuidTypeHint(JavaField field) {
- if (!field.getRawType().isEquivalentTo(UUID.class)) {
- return false;
- }
- return field
- .tryGetAnnotationOfType("org.hibernate.annotations.JdbcTypeCode")
- .flatMap(annotation -> annotation.get("value"))
- .filter(value -> value instanceof Number number && number.intValue() == SqlTypes.UUID)
- .isPresent();
- }
-}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java
new file mode 100644
index 00000000..95666fa6
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java
@@ -0,0 +1,83 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+
+/**
+ * The composition root assembles; it does not implement application ports (BOOT-017).
+ *
+ *
A class that implements an {@code application-core} port over an external system is an
+ * outbound adapter, and an outbound adapter belongs to an {@code adapter/outbound/*} leaf. Four
+ * such implementations had accumulated here — two Micrometer metric sinks and two provider
+ * registries — and nothing objected, because the composition root is where their inputs happen to
+ * be assembled and that felt like a reason.
+ *
+ *
It is not one. This repository already had the counter-example in place: {@code
+ * LoggingNotificationAudit} implements {@code NotificationAuditPort} and lives in {@code
+ * adapter/outbound/notification}, with the composition root doing nothing but wire it. The
+ * Micrometer sink next to it now does the same. Without a rule, the pressure only ever runs one way
+ * — every new observability port lands in bootstrap because the previous one did — and a derived
+ * service that replaces the composition root inherits adapter implementations it has to rewrite.
+ *
+ *
What this rule does not cover
+ *
+ *
{@code shared-contract} seams are excluded deliberately. That leaf holds skeleton-wide
+ * operational contracts — tracing, metrics naming, the runtime environment vocabulary — which the
+ * composition root is a legitimate participant in rather than a consumer of. Extending the rule
+ * there would forbid the composition root from implementing contracts that exist for it.
+ *
+ *
Bootstrap-local interfaces are also outside it. {@code EnvironmentSecretSource} implements
+ * {@code dev.caskeleton.bootstrap.runtime.SecretSource}, a port this leaf declares and owns; a
+ * strategy over a leaf's own interface is not a misplaced adapter, and an earlier reading that
+ * counted it as one was simply wrong about where the interface lived.
+ */
+@AnalyzeClasses(
+ packages = "dev.caskeleton.bootstrap",
+ importOptions = ProductionClassImportOption.class)
+class CompositionRootBoundaryArchitectureTest {
+
+ /**
+ * {@code MeteredDistributedLockPort} — a decorator, not an adapter.
+ *
+ *
It implements {@code DistributedLockPort} in order to wrap another implementation of it with
+ * metering, and "which ports are metered" is an assembly decision, which is this leaf's job. The
+ * argument on the other side is real — metering is a cross-cutting concern like retry or caching,
+ * and those collecting here is how a composition root becomes large again — so the threshold is
+ * stated rather than left to taste: at the second such decorator, they move to {@code
+ * adapter:outbound:support} together. One is a special case; two is a category.
+ */
+ private static final String METERED_DISTRIBUTED_LOCK_PORT =
+ "dev.caskeleton.bootstrap.autoconfigure.lock.MeteredDistributedLockPort";
+
+ private static final DescribedPredicate ALLOWLISTED =
+ new DescribedPredicate<>("allowlisted from the composition-root boundary") {
+ @Override
+ public boolean test(JavaClass candidate) {
+ String name = candidate.getFullName();
+ return name.equals(METERED_DISTRIBUTED_LOCK_PORT)
+ || name.startsWith(METERED_DISTRIBUTED_LOCK_PORT + "$");
+ }
+ };
+
+ @ArchTest
+ static final ArchRule COMPOSITION_ROOT_DOES_NOT_IMPLEMENT_APPLICATION_OR_DOMAIN_PORTS =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.bootstrap..")
+ .and(DescribedPredicate.not(ALLOWLISTED))
+ .should()
+ .implement(
+ JavaClass.Predicates.resideInAnyPackage(
+ "dev.caskeleton.application..", "dev.caskeleton.domain.."))
+ .as(
+ "BOOT-017: the composition root wires adapters, it does not implement application or "
+ + "domain ports. A class implementing one is an outbound adapter and belongs in "
+ + "an adapter/outbound/* leaf — see LoggingNotificationAudit and "
+ + "MicrometerNotificationMetrics in adapter/outbound/notification for the shape.")
+ .allowEmptyShould(true);
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java
index 555bc3c4..7718fbc7 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java
@@ -31,27 +31,29 @@ class DomainFeatureOnboardingContractTest {
ROOT + ".application.port.ListFeatureAggregatesWritePort");
assertThat(
- CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE
+ ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY
+ ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS
+ ApplicationBoundaryArchitectureTest
+ .READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY
+ ApplicationBoundaryArchitectureTest
+ .USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES
+ TypeBoundaryArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
@@ -75,30 +77,33 @@ class DomainFeatureOnboardingContractTest {
ROOT + ".adapter.inbound.web.dto.FeatureAggregateResponse",
ROOT + ".adapter.inbound.web.controller.FeatureAggregateController");
- assertThat(CleanArchitectureTest.DOMAIN_IS_PURE.evaluate(ONBOARDING_CLASSES).hasViolation())
+ assertThat(
+ DomainPurityArchitectureTest.DOMAIN_IS_PURE.evaluate(ONBOARDING_CLASSES).hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT
+ ApplicationBoundaryArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION
+ ApplicationBoundaryArchitectureTest.MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY
+ ApplicationBoundaryArchitectureTest
+ .USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS
+ AdapterBoundaryArchitectureTest
+ .PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
assertThat(
- CleanArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO
+ TypeBoundaryArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO
.evaluate(ONBOARDING_CLASSES)
.hasViolation())
.isFalse();
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java
new file mode 100644
index 00000000..8b7a27ac
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java
@@ -0,0 +1,250 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaConstructor;
+import com.tngtech.archunit.core.domain.JavaField;
+import com.tngtech.archunit.core.domain.JavaModifier;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import dev.caskeleton.domain.stereotype.AggregateRoot;
+import dev.caskeleton.domain.stereotype.DomainEvent;
+import dev.caskeleton.domain.stereotype.ValueObject;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * The domain model's own rules: framework purity, the shape of value objects and aggregates, and
+ * the transport-neutrality of events. Nothing here reads a package outside {@code domain-core}.
+ *
+ *
도메인 모델 자체의 규칙 — 프레임워크 순수성, 값 객체와 애그리게이트의 형태, 이벤트의 전송 중립성.
+ *
+ *
{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by
+ * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields
+ * inside a nested class — the rules would have stopped running while the build stayed green
+ * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share
+ * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest}
+ * holds the full rule inventory so none can be lost in a move like this one.
+ */
+@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class)
+class DomainPurityArchitectureTest {
+
+ @ArchTest
+ static final ArchRule DOMAIN_IS_PURE =
+ noClasses()
+ .that()
+ .resideInAPackage("..domain..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "org.springframework..",
+ "jakarta.persistence..",
+ "javax.persistence..",
+ "jakarta.servlet..",
+ "org.hibernate..",
+ "lombok..",
+ "..application..",
+ "..adapter..",
+ "..bootstrap..",
+ "..service..",
+ "..infra..",
+ "..presentation..",
+ "..cmd..")
+ .as(
+ "domain-core must be framework-neutral POJO — no Spring / JPA / "
+ + "Hibernate / Lombok generated bytecode "
+ + "(feature-architecture-enforcement-rules D3)")
+ .allowEmptyShould(true);
+
+ // ---- feature-domain-modeling-guardrails (modeling-specific rules; owner = this branch) ----
+ // Domain framework-neutrality is delegated to DOMAIN_IS_PURE above
+ // (feature-architecture-enforcement-rules D3). The rules below add the
+ // modeling-specific guardrails this branch owns: logger ban (D3), value-object
+ // invariant surface (D5/D6), aggregate mutator visibility (D7), and transport-free
+ // domain events (D4/D8). They key off the dev.caskeleton.domain.stereotype markers.
+
+ @ArchTest
+ static final ArchRule DOMAIN_HAS_NO_LOGGER =
+ noClasses()
+ .that()
+ .resideInAPackage("..domain..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage(
+ "org.slf4j..",
+ "java.util.logging..",
+ "ch.qos.logback..",
+ "org.apache.logging.log4j..")
+ .as(
+ "D3 DOMAIN_HAS_NO_LOGGER: the domain layer must not depend on a logging "
+ + "framework (SLF4J / java.util.logging / Logback / Log4j). An invariant "
+ + "violation is expressed as a domain exception carrying a safe reason "
+ + "value (noun enum); the application layer translates that into a log "
+ + "line. This is a ca-tmpl convention (UNSUPPORTED_DECISION — no official "
+ + "standard source), kept SEPARATE from DOMAIN_IS_PURE to preserve the "
+ + "owner boundary of feature-architecture-enforcement-rules D3 "
+ + "(feature-domain-modeling-guardrails D3, §2)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR =
+ classes()
+ .that()
+ .areAnnotatedWith(ValueObject.class)
+ .or()
+ .resideInAPackage("..domain.vo..")
+ .should(notHaveAPublicNoArgConstructor())
+ .as(
+ "D5/D6 VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR: a @ValueObject "
+ + "(or any type under ..domain.vo..) must validate its invariant on the "
+ + "sole construction path and must NOT expose a public no-arg constructor "
+ + "— an empty constructor is an invariant-bypassing back door. Records "
+ + "with components satisfy this automatically "
+ + "(feature-domain-modeling-guardrails D5/D6, §3)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC =
+ methods()
+ .that()
+ .haveNameMatching("set.*")
+ .and()
+ .areDeclaredInClassesThat()
+ .areAnnotatedWith(AggregateRoot.class)
+ .should()
+ .notBePublic()
+ .as(
+ "D7 AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC: a @AggregateRoot mutates its "
+ + "state only through intention-revealing methods that enforce invariants "
+ + "— a raw set* mutator must be package-private/protected (the visibility "
+ + "Vernon Option A ORM-external reconstruction relies on). ArchUnit only "
+ + "statically reaches the set* surface; other state-changing methods stay "
+ + "a code-review concern (feature-domain-modeling-guardrails D7, §4)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule DOMAIN_EVENTS_ARE_RECORDS =
+ classes()
+ .that()
+ .areAnnotatedWith(DomainEvent.class)
+ .should(beRecordTypes())
+ .as(
+ "D4/D8 DOMAIN_EVENTS_ARE_RECORDS: a @DomainEvent is an immutable fact and "
+ + "must be declared as a record (feature-domain-modeling-guardrails D4/D8, §5)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule DOMAIN_EVENTS_ARE_TRANSPORT_FREE =
+ noClasses()
+ .that()
+ .areAnnotatedWith(DomainEvent.class)
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("org.apache.kafka..", "org.springframework.http..", "jakarta.ws.rs..")
+ .as(
+ "D4/D8 DOMAIN_EVENTS_ARE_TRANSPORT_FREE: a @DomainEvent carries domain data "
+ + "only and must not depend on a broker/wire/HTTP type (Kafka SDK, Spring "
+ + "HTTP, JAX-RS). Translating a domain event into an integration/wire event "
+ + "is the application/infrastructure boundary's job. UNSUPPORTED_IMPL_DECISION: "
+ + "the forbidden list enumerates only the SDKs ca-tmpl could plausibly add — "
+ + "extend it when a new broker/transport is introduced "
+ + "(feature-domain-modeling-guardrails D4/D8, §5)")
+ .allowEmptyShould(true);
+
+ // ---- feature-persistence-auditing-contract D2 (domain stays free of audit metadata) ----
+ // DOMAIN_IS_PURE already bans jakarta.persistence.. / org.springframework.. (incl.
+ // org.springframework.data..) from ..domain.., so @CreatedDate / @Column / AuditableEntity
+ // cannot reach the domain. This rule adds the audit-specific guardrail D2 owns: even as
+ // plain POJO fields, a domain aggregate must carry ZERO audit metadata — who/when
+ // created/modified is an adapter-persistence concern (AuditableEntity @MappedSuperclass),
+ // and putting it in the domain would invert the dependency (the domain would know "who
+ // logged in") and leak the infrastructure concern inward.
+ @ArchTest
+ static final ArchRule DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS =
+ noClasses()
+ .that()
+ .resideInAPackage("..domain..")
+ .should(declareAnAuditField())
+ .as(
+ "D2 DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS: a domain class must not declare "
+ + "an audit field (createdAt / updatedAt / createdBy / updatedBy) — audit "
+ + "metadata lives only on the adapter-persistence AuditableEntity "
+ + "@MappedSuperclass so the domain aggregate carries none of it "
+ + "(feature-persistence-auditing-contract D2)")
+ .allowEmptyShould(true);
+
+ // ---- feature-domain-modeling-guardrails custom conditions ----
+
+ private static final Set AUDIT_FIELD_NAMES =
+ Set.of("createdat", "updatedat", "createdby", "updatedby");
+
+ private static ArchCondition declareAnAuditField() {
+ return new ArchCondition<>(
+ "declare an audit field " + "(createdAt / updatedAt / createdBy / updatedBy)") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ for (JavaField field : item.getFields()) {
+ if (AUDIT_FIELD_NAMES.contains(field.getName().toLowerCase(Locale.ROOT))) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " declares audit field '"
+ + field.getName()
+ + "' — audit metadata must live only on the adapter-persistence "
+ + "AuditableEntity @MappedSuperclass, not on a domain class "
+ + "(feature-persistence-auditing-contract D2)"));
+ }
+ }
+ }
+ };
+ }
+
+ private static ArchCondition notHaveAPublicNoArgConstructor() {
+ return new ArchCondition<>("not have a public no-arg constructor") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ for (JavaConstructor constructor : item.getConstructors()) {
+ if (constructor.getRawParameterTypes().isEmpty()
+ && constructor.getModifiers().contains(JavaModifier.PUBLIC)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " exposes a public no-arg constructor — "
+ + "a value object must validate its invariant on the sole "
+ + "construction path and offer no empty back door "
+ + "(feature-domain-modeling-guardrails D5/D6)"));
+ }
+ }
+ }
+ };
+ }
+
+ private static ArchCondition beRecordTypes() {
+ return new ArchCondition<>("be a record") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ if (!item.isRecord()) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " is annotated @DomainEvent but is not a "
+ + "record — a domain event is an immutable fact and must be a "
+ + "record (feature-domain-modeling-guardrails D4/D8)"));
+ }
+ }
+ };
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java
new file mode 100644
index 00000000..d6226bb6
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java
@@ -0,0 +1,663 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
+import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
+import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaField;
+import com.tngtech.archunit.core.domain.JavaMethod;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import dev.caskeleton.domain.identifier.ResourceId;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Pattern;
+import org.hibernate.type.SqlTypes;
+
+/**
+ * Bans on specific APIs and idioms. Each entry is one incident rather than a principle: a {@code
+ * new BigDecimal(double)} that lost money, an {@code InheritableThreadLocal} that leaked across a
+ * pooled thread, a {@code ProblemDetail} that bypassed the error envelope. They are grouped by
+ * being unrelated to each other — which is exactly why they were unreadable inside a 2,792-line
+ * class.
+ *
+ *
개별 API·관용구 금지 규칙 — 각각이 한 번의 사고에서 나온 항목이다.
+ *
+ *
{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by
+ * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields
+ * inside a nested class — the rules would have stopped running while the build stayed green
+ * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share
+ * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest}
+ * holds the full rule inventory so none can be lost in a move like this one.
+ */
+@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class)
+class ForbiddenApiArchitectureTest {
+
+ private static ArchCondition resideInAllowedSharedContractPackage() {
+ return new ArchCondition<>("reside in an allowed shared operational contract package") {
+ private final Set allowedPackages =
+ Set.of(
+ "dev.caskeleton.shared",
+ "dev.caskeleton.shared.response",
+ "dev.caskeleton.shared.request",
+ "dev.caskeleton.shared.error",
+ "dev.caskeleton.shared.operation",
+ "dev.caskeleton.shared.headers",
+ "dev.caskeleton.shared.logging",
+ "dev.caskeleton.shared.tracing",
+ "dev.caskeleton.shared.metrics",
+ "dev.caskeleton.shared.registry",
+ "dev.caskeleton.shared.annotation",
+ // Not this module at all — see the defect note on the rule above. The Mongo leaf has
+ // a
+ // tenancy.shared package of its own and only began tripping this rule when that leaf
+ // joined the composition root's analysis scope.
+ "dev.caskeleton.adapter.outbound.mongo.advanced.tenancy.shared",
+ // The five master switches and their strict parse. Operational rather than domain:
+ // it names environment keys and rejects unreadable values, and it lives here because
+ // the environment post-processor needs it before a Spring context exists, so it must
+ // not depend on Spring.
+ "dev.caskeleton.shared.activation",
+ // feature-authentication-authorization-contract: the Permission
+ // (resource:action) value object is a Java-only operational
+ // authorization contract consumed by AuthorizationPort.
+ "dev.caskeleton.shared.security",
+ // feature-runtime-context-propagation-contract: the domain-context
+ // propagation port + default ThreadLocal strategy + factory
+ // (swappable abstraction, the rate-limit pattern).
+ "dev.caskeleton.shared.concurrency");
+
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ String packageName = item.getPackageName();
+ boolean allowed = false;
+ for (String allowedPackage : allowedPackages) {
+ if (packageName.equals(allowedPackage) || packageName.startsWith(allowedPackage + ".")) {
+ allowed = true;
+ break;
+ }
+ }
+ if (!allowed) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " resides in "
+ + packageName
+ + ", which is not an allowed shared operational contract package"));
+ }
+ }
+ };
+ }
+
+ @ArchTest
+ // KNOWN DEFECT, deliberately left as found. Two things are wrong and they mask each other: the
+ // subject pattern "..shared.." CleanArchitectureRuleSupport.matches any package named shared
+ // anywhere rather than this module,
+ // and the allowlist below holds the bare module root while the check
+ // CleanArchitectureRuleSupport.matches by prefix — so every
+ // package inside dev.caskeleton.shared is blessed automatically. The rule therefore catches
+ // nothing in the module it is named after, and does catch unrelated leaves that happen to have a
+ // "shared" package. Fixing it needs the violation fixture to move inside dev.caskeleton.shared,
+ // which puts it in this rule's own analysis scope, which needs the fixture-scanning structure
+ // reworked. That is a separate change; see the Wave 1 progress note.
+ static final ArchRule SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES =
+ classes()
+ .that()
+ .resideInAPackage("..shared..")
+ .should(resideInAllowedSharedContractPackage())
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName(
+ "com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator")
+ .as(
+ "LaissezFaireSubTypeValidator allows any subtype during polymorphic "
+ + "deserialization and is the CVE-2019-14379 RCE entry point "
+ + "(feature-boundary-validation-mapping-contract B5)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .callMethodWhere(target(name("enableDefaultTyping")))
+ .as(
+ "ObjectMapper.enableDefaultTyping() (deprecated since Jackson 2.10) opens "
+ + "polymorphic deserialization to all classes — the documented "
+ + "CVE-2019-14379 RCE vector (feature-boundary-validation-mapping-contract B5). "
+ + "Use @JsonTypeInfo(use = NAME) + @JsonSubTypes, or "
+ + "activateDefaultTyping(BasicPolymorphicTypeValidator) with an explicit "
+ + "allowlist — the LaissezFaireSubTypeValidator class reference rule "
+ + "still blocks the unsafe variant.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_INHERITABLE_THREAD_LOCAL =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName("java.lang.InheritableThreadLocal")
+ .as(
+ "InheritableThreadLocal does not propagate cleanly to virtual threads "
+ + "and leaks context across pooled carrier threads — use MDC (SLF4J "
+ + "2.0+) or RequestContextHolder instead "
+ + "(feature-boundary-validation-mapping-contract B6)")
+ .allowEmptyShould(true);
+
+ // ---- feature-runtime-context-propagation-contract (domain extension of B6) ----
+ // Strategy guard for the domain-context propagation seam. The shipped default is
+ // ThreadLocal (DomainContextStrategy.THREAD_LOCAL — plain ThreadLocal, no preview
+ // API); the MICROMETER / SCOPED_VALUE strategies are reserved-but-unshipped. This
+ // rule keeps the two Java 21 PREVIEW primitives (ScopedValue / StructuredTaskScope)
+ // out of production code until the SCOPED_VALUE strategy is actually activated
+ // through the factory seam. The build has no --enable-preview (build fact C1), so
+ // those APIs cannot even compile today — the compile gate is the first line of
+ // defence and is why a compilable violation fixture is infeasible; this rule is the
+ // dormant defence-in-depth that fires the moment --enable-preview is enabled and
+ // someone reaches for the preview primitive without adding the strategy + factory
+ // case. It is NOT the deferred S4/D7 activation-correctness rule
+ // (UNSUPPORTED_IMPL_DECISION — rule shape intentionally not invented). The
+ // implicit-inheritance ban (S3, the ThreadLocal default's contract) cross-cites
+ // NO_INHERITABLE_THREAD_LOCAL above (owner: boundary-validation B6).
+
+ @ArchTest
+ static final ArchRule DOMAIN_CONTEXT_PROPAGATION_PRIMITIVES_STAY_UNSHIPPED =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .haveNameMatching(
+ "(java\\.lang\\.ScopedValue"
+ + "|java\\.util\\.concurrent\\.StructuredTaskScope)(\\$.*)?")
+ .as(
+ "the domain-context default is ThreadLocal; the ScopedValue / "
+ + "StructuredTaskScope (preview) strategy must not appear in production "
+ + "code until activated through the DomainContextStrategy + factory seam "
+ + "(feature-runtime-context-propagation-contract S2)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_PROBLEM_DETAIL_USAGE =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName("org.springframework.http.ProblemDetail")
+ .as(
+ "D5: RFC 7807 ProblemDetail is explicitly rejected — error responses must use "
+ + "the shared Envelope shape "
+ + "(feature-boundary-validation-mapping-contract D5). Production code is "
+ + "scanned only — test fixtures in ..architecture.violations.. are out of "
+ + "scope under ImportOption.DoNotIncludeTests.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING =
+ classes()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should(notReferenceMediaTypeString("application/merge-patch+json"))
+ .as(
+ "B2: RFC 7396 application/merge-patch+json content type is explicitly rejected "
+ + "— PATCH endpoints must use JSON with JsonNullable 3-state "
+ + "(feature-boundary-validation-mapping-contract B2)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule
+ MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS =
+ methods()
+ .that()
+ .areDeclaredInClassesThat()
+ .resideInAPackage("..adapter.outbound.messaging..")
+ .and()
+ .areDeclaredInClassesThat()
+ .areNotAnnotatedWith("org.springframework.context.annotation.Configuration")
+ .and()
+ .areDeclaredInClassesThat()
+ .areNotAnnotatedWith(
+ "org.springframework.boot.context.properties.ConfigurationProperties")
+ .and()
+ .arePublic()
+ .and()
+ .areNotStatic()
+ .should(
+ notReturnAdapterTypesIncludingGenericArguments(
+ "..adapter.outbound..",
+ "..adapter.inbound.web..",
+ "..adapter.outbound.persistence.."))
+ .as(
+ "B7 messaging hardening: every public non-static messaging method must keep "
+ + "adapter-local types out of direct and recursive generic return positions; "
+ + "narrow public static composition bridges are the only allowed adapter-local "
+ + "return seam")
+ .allowEmptyShould(true);
+
+ private static ArchCondition notReturnAdapterTypesIncludingGenericArguments(
+ String... forbiddenPackages) {
+ DescribedPredicate forbidden =
+ JavaClass.Predicates.resideInAnyPackage(forbiddenPackages);
+ return new ArchCondition<>(
+ "not return adapter-local types directly or through generic arguments") {
+ @Override
+ public void check(JavaMethod method, ConditionEvents events) {
+ for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) {
+ if (forbidden.test(involved)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ method,
+ "Method "
+ + method.getFullName()
+ + " leaks "
+ + involved.getName()
+ + " through its return type (directly or as a generic argument)"
+ + " — B7 permits only narrow static composition bridges for "
+ + "adapter-local types"));
+ }
+ }
+ }
+ };
+ }
+
+ // ---- feature-api-contract-baseline D19 (resource URL naming, AIP-122) ----
+
+ @ArchTest
+ static final ArchRule CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122 =
+ classes()
+ .that()
+ .resideInAPackage("..adapter.inbound.web..controller..")
+ .should(haveAip122CompliantRequestMappingPaths())
+ .as(
+ "D19: controller @RequestMapping path segments must follow AIP-122 — lowercase "
+ + "first char + ASCII alphanumerics ([a-z][a-zA-Z0-9]*), so kebab-case "
+ + "(/work-logs), CamelCase (/Tickets) and SNAKE_CASE are rejected. "
+ + "{pathVariables} and AIP-136 colon-verb custom methods "
+ + "(/worklogs:batchCreate) are allowed (feature-api-contract-baseline D19)")
+ .allowEmptyShould(true);
+
+ private static final Pattern AIP122_SEGMENT = Pattern.compile("[a-z][a-zA-Z0-9]*");
+ private static final Pattern AIP136_VERB = Pattern.compile("[a-zA-Z][a-zA-Z0-9]*");
+ private static final Set MAPPING_ANNOTATION_TYPES =
+ Set.of(
+ "org.springframework.web.bind.annotation.RequestMapping",
+ "org.springframework.web.bind.annotation.GetMapping",
+ "org.springframework.web.bind.annotation.PostMapping",
+ "org.springframework.web.bind.annotation.PutMapping",
+ "org.springframework.web.bind.annotation.PatchMapping",
+ "org.springframework.web.bind.annotation.DeleteMapping");
+
+ private static ArchCondition haveAip122CompliantRequestMappingPaths() {
+ return new ArchCondition<>("have AIP-122 compliant @RequestMapping path segments") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ checkMappingPaths(item, item.getAnnotations(), events);
+ for (JavaMethod m : item.getMethods()) {
+ checkMappingPaths(item, m.getAnnotations(), events);
+ }
+ }
+ };
+ }
+
+ private static void checkMappingPaths(
+ JavaClass owner, Set extends JavaAnnotation>> annotations, ConditionEvents events) {
+ for (JavaAnnotation> annotation : annotations) {
+ if (!MAPPING_ANNOTATION_TYPES.contains(annotation.getRawType().getName())) {
+ continue;
+ }
+ for (String key : List.of("value", "path")) {
+ annotation
+ .get(key)
+ .ifPresent(
+ value -> {
+ for (String path : toPathStrings(value)) {
+ for (String segment : path.split("/", 0)) {
+ validatePathSegment(owner, segment, path, events);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ private static List toPathStrings(Object value) {
+ if (value instanceof String s) {
+ return List.of(s);
+ }
+ if (value instanceof Object[] arr) {
+ List out = new ArrayList<>();
+ for (Object e : arr) {
+ if (e != null) {
+ out.add(e.toString());
+ }
+ }
+ return out;
+ }
+ return List.of();
+ }
+
+ private static void validatePathSegment(
+ JavaClass owner, String segment, String path, ConditionEvents events) {
+ if (segment.isEmpty() || (segment.startsWith("{") && segment.endsWith("}"))) {
+ return; // empty segment or {pathVariable}
+ }
+ String[] colonParts = segment.split(":", 2);
+ String resource = colonParts[0];
+ if (!resource.isEmpty() && !AIP122_SEGMENT.matcher(resource).matches()) {
+ events.add(
+ SimpleConditionEvent.violated(
+ owner,
+ "Class "
+ + owner.getName()
+ + " maps non-AIP-122 path segment '"
+ + segment
+ + "' in '"
+ + path
+ + "' — use lowercase [a-z][a-zA-Z0-9]* (no kebab/Camel/snake)"));
+ return;
+ }
+ if (colonParts.length == 2 && !AIP136_VERB.matcher(colonParts[1]).matches()) {
+ events.add(
+ SimpleConditionEvent.violated(
+ owner,
+ "Class "
+ + owner.getName()
+ + " maps non-AIP-136 colon-verb '"
+ + segment
+ + "' in '"
+ + path
+ + "'"));
+ }
+ }
+
+ private static ArchCondition notReferenceMediaTypeString(String mediaType) {
+ return new ArchCondition<>("not reference media type string '" + mediaType + "'") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ // The realistic carriers — @RequestMapping(consumes=...),
+ // @PostMapping(consumes=...), and so on — sit on classes AND methods.
+ // Bytecode does not retain String initializer values for non-final
+ // fields, so free-form references inside method bodies cannot be
+ // detected at the ArchUnit level. We catch the annotation carriers
+ // (class and method) which is where the realistic usage lives.
+ scanAnnotations(item, item.getAnnotations(), mediaType, events);
+ for (JavaMethod m : item.getMethods()) {
+ scanAnnotations(item, m.getAnnotations(), mediaType, events);
+ }
+ }
+ };
+ }
+
+ private static void scanAnnotations(
+ JavaClass owner,
+ Set extends JavaAnnotation>> annotations,
+ String mediaType,
+ ConditionEvents events) {
+ for (JavaAnnotation> annotation : annotations) {
+ // Annotation properties on Spring's @RequestMapping family carry their values as
+ // String[] which boxes to Object[]. Some ArchUnit versions also expose property
+ // values as plain String. Walk both shapes and also fall back to the rendered
+ // annotation text so single-value carriers (e.g. MediaType constants resolved at
+ // bytecode level) are caught.
+ for (Object v : annotation.getProperties().values()) {
+ if (CleanArchitectureRuleSupport.matches(v, mediaType)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ owner,
+ "Class "
+ + owner.getName()
+ + " uses '"
+ + mediaType
+ + "' via @"
+ + annotation.getRawType().getSimpleName()));
+ return;
+ }
+ }
+ if (annotation.toString().contains(mediaType)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ owner,
+ "Class "
+ + owner.getName()
+ + " uses '"
+ + mediaType
+ + "' (detected in @"
+ + annotation.getRawType().getSimpleName()
+ + " text)"));
+ }
+ }
+ }
+
+ // ---- feature-resource-identifier-contract D17 (decision SSOT: resource-identifier branch) ----
+
+ @ArchTest
+ static final ArchRule NO_LONG_ID_PK =
+ fields()
+ .that()
+ .areDeclaredInClassesThat()
+ .resideInAPackage("..domain..")
+ .and()
+ .haveNameMatching("id")
+ .should()
+ .haveRawType(assignableTo(ResourceId.class))
+ .as(
+ "D17 NO_LONG_ID_PK: a domain entity 'id' field must be a ResourceId value object"
+ + " (e.g. WorkLogId), never Long/long/int/Integer"
+ + " (feature-resource-identifier-contract D17). JPA @Id UUID columns in"
+ + " ..adapter.outbound.persistence.. are out of scope — they store the UUID as"
+ + " the PostgreSQL native uuid type per D10.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_UUID_RANDOM_IN_CONTROLLER =
+ noClasses()
+ .that()
+ .resideInAnyPackage("..adapter.inbound.web..controller..", "..application..")
+ .should()
+ .callMethod(UUID.class, "randomUUID")
+ .orShould()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName("com.github.f4b6a3.uuid.UuidCreator")
+ .as(
+ "D5/D17 NO_UUID_RANDOM_IN_CONTROLLER: controllers and the application layer "
+ + "(use cases / services) must not generate resource identifiers directly "
+ + "(UUID.randomUUID / UuidCreator) — inject the domain WorkLogIdFactory port "
+ + "instead (feature-resource-identifier-contract D5/D17). Scope follows the "
+ + "D17 decision text (controller / service / use case); request/trace-id "
+ + "generation in web filters is a distinct concern owned by D18 / "
+ + "feature-distributed-tracing-contract and is intentionally out of scope.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_MATH_RANDOM_FOR_ID =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .callMethod(Math.class, "random")
+ .as(
+ "D9/D17 NO_MATH_RANDOM_FOR_ID: Math.random() is not a CSPRNG and must never "
+ + "seed an identifier (feature-resource-identifier-contract D9/D17).")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_VARCHAR_255_FOR_ID_COLUMN =
+ fields()
+ .that()
+ .areAnnotatedWith("jakarta.persistence.Column")
+ .and()
+ .haveNameMatching(".*[iI]d")
+ .should(haveExplicitIdColumnMapping())
+ .as(
+ "D17 NO_VARCHAR_255_FOR_ID_COLUMN: an @Column-mapped id field must declare an "
+ + "explicit non-default length or a portable UUID type hint — never fall "
+ + "back to varchar(255) (feature-resource-identifier-contract D17).")
+ .allowEmptyShould(true);
+
+ // ---- feature-streaming-response-contract D3 (event/server-push streaming ban) ----
+
+ @ArchTest
+ static final ArchRule NO_SSE_EMITTER =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName(
+ "org.springframework.web.servlet.mvc.method.annotation.SseEmitter")
+ .as(
+ "D3 NO_SSE_EMITTER: production code must not import SseEmitter — "
+ + "SSE server-push streaming (W3C EventSource / text/event-stream) is "
+ + "not supported in ca-skeleton (feature-streaming-response-contract D3). "
+ + "Production code is scanned only — test fixtures in "
+ + "..architecture.violations.. are out of scope under "
+ + "ImportOption.DoNotIncludeTests.")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_RESPONSE_BODY_EMITTER =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .haveFullyQualifiedName(
+ "org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter")
+ .as(
+ "D3 NO_RESPONSE_BODY_EMITTER: production code must not import "
+ + "ResponseBodyEmitter — it is the SSE base class and incremental "
+ + "object-emit surface (SPRING-ASYNC-C3) treated as server-push "
+ + "(UNSUPPORTED_IMPL_DECISION④). Note: StreamingResponseBody "
+ + "(large-file download, request-response model) is NOT blocked — "
+ + "it is owned by feature-file-resource-handling-contract D8 "
+ + "(feature-streaming-response-contract D3).")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule NO_WEBSOCKET_HANDLER =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("org.springframework.web.socket..", "jakarta.websocket..")
+ .as(
+ "D3 NO_WEBSOCKET_HANDLER: production code must not depend on "
+ + "org.springframework.web.socket.. or jakarta.websocket.. — "
+ + "these cover the spring-websocket handler/STOMP surfaces and "
+ + "Jakarta @ServerEndpoint (RFC 6455 full-duplex WebSocket model). "
+ + "Full-duplex streaming is not supported in ca-skeleton "
+ + "(feature-streaming-response-contract D3). Production code is "
+ + "scanned only under ImportOption.DoNotIncludeTests.")
+ .allowEmptyShould(true);
+
+ // ---- feature-secrets-config-source-contract D3/D10 (no-runtime-reload static enforcement) ----
+
+ @ArchTest
+ static final ArchRule NO_REFRESH_SCOPE_ANYWHERE =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .beAnnotatedWith("org.springframework.cloud.context.config.annotation.RefreshScope")
+ .as(
+ "D3/D10 NO_REFRESH_SCOPE_ANYWHERE: secrets default to no-runtime-reload "
+ + "(rotation = restart validation), so @RefreshScope is banned across the "
+ + "whole production tree — it is the in-process config-reload machinery the "
+ + "VAULT-DYN-C2 dynamic-reload rejection forbids "
+ + "(feature-secrets-config-source-contract D3/D10). The ban is total (no "
+ + "carve-out): an explicit rotation handler (e.g. JWT 24h overlap, owned by "
+ + "another branch) rotates via a mutable holder + scheduled swap, never "
+ + "@RefreshScope. spring-cloud-context is not on the production classpath, so "
+ + "the annotation is referenced by fully-qualified name. Production code is "
+ + "scanned only — the fixture in ..architecture.violations.secrets.. is out "
+ + "of scope under ImportOption.DoNotIncludeTests.")
+ .allowEmptyShould(true);
+
+ // ---- feature-schema-serialization-contract D3 (BigDecimal precision trap ban) ----
+
+ @ArchTest
+ static final ArchRule NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .should()
+ .callConstructor(BigDecimal.class, double.class)
+ .orShould()
+ .callConstructor(BigDecimal.class, float.class)
+ .as(
+ "D3 NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR: new BigDecimal(double) / "
+ + "new BigDecimal(float) captures the binary floating-point error "
+ + "(new BigDecimal(0.1) == 0.1000000000000000055...) instead of the exact "
+ + "decimal — money/decimal values must be built from a String "
+ + "(new BigDecimal(\"0.1\") or BigDecimal.valueOf(double)) "
+ + "(feature-schema-serialization-contract D3 / SBMS-C3). Production code is "
+ + "scanned only — test fixtures in ..architecture.violations.. are out of "
+ + "scope under ImportOption.DoNotIncludeTests.")
+ .allowEmptyShould(true);
+
+ private static ArchCondition haveExplicitIdColumnMapping() {
+ return new ArchCondition<>(
+ "declare an explicit non-default length or portable UUID type hint") {
+ @Override
+ public void check(JavaField field, ConditionEvents events) {
+ field
+ .tryGetAnnotationOfType("jakarta.persistence.Column")
+ .ifPresent(
+ column -> {
+ int length = column.get("length").map(v -> ((Number) v).intValue()).orElse(255);
+ boolean explicit = length != 255 || hasPortableUuidTypeHint(field);
+ if (!explicit) {
+ events.add(
+ SimpleConditionEvent.violated(
+ field,
+ "Field "
+ + field.getFullName()
+ + " maps an id column with the implicit "
+ + "varchar(255) default — declare an explicit length or "
+ + "@JdbcTypeCode(SqlTypes.UUID) for UUID fields "
+ + "(feature-resource-identifier-contract D17)"));
+ }
+ });
+ }
+ };
+ }
+
+ private static boolean hasPortableUuidTypeHint(JavaField field) {
+ if (!field.getRawType().isEquivalentTo(UUID.class)) {
+ return false;
+ }
+ return field
+ .tryGetAnnotationOfType("org.hibernate.annotations.JdbcTypeCode")
+ .flatMap(annotation -> annotation.get("value"))
+ .filter(value -> value instanceof Number number && number.intValue() == SqlTypes.UUID)
+ .isPresent();
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java
new file mode 100644
index 00000000..d8911db9
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java
@@ -0,0 +1,139 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchRule;
+import java.util.List;
+
+/**
+ * The composition root's startup failures must carry an exit code (BOOT-003).
+ *
+ *
{@code StartupErrorCode} classifies a failed start into four causes and gives each one a
+ * sysexits exit status — 78 for a configuration error, 70 for a failed migration, 71 for a profile
+ * conflict, 72 for a required adapter that is off. That status only reaches the JVM because {@code
+ * StartupFailureException} implements {@code ExitCodeGenerator}, and the structured failure log
+ * with {@code startup.phase} / {@code error.code} only exists because {@code StartupFailures} emits
+ * it before returning the exception.
+ *
+ *
None of that happens for {@code throw new IllegalStateException(...)}. This leaf held 65
+ * places that constructed one; 60 of them were startup validation and went through none of the
+ * machinery above, so the process died with a generic exit 1, no {@code error.code} field, and
+ * Spring Boot's full "Application run failed" stacktrace instead of the one line an operator needs.
+ * The classification existed and roughly a third of the throws used it.
+ *
+ *
The allowlist
+ *
+ *
Three classes may still construct {@code IllegalStateException} for a reason of their own, and
+ * the distinction is not stylistic. {@code StartupFailures} reports an operator configured
+ * something wrong; these three report a caller or the JVM broke an invariant, which
+ * is not a deployment problem and must not be dressed as one — assigning it exit 78 would tell an
+ * operator to go and fix a configuration file that is perfectly correct. The remaining two entries
+ * are the failure machinery itself.
+ *
+ *
{@code IllegalArgumentException} is deliberately not covered. Rejecting a bad method argument
+ * is a different act from rejecting a bad deployment, and giving it an exit code would say the
+ * deployment was misconfigured when a caller passed the wrong value.
+ *
+ *
The allowlist is spelled out here rather than expressed as a package or naming pattern
+ * precisely so that it cannot grow quietly: adding a class to it is a visible edit to this file
+ * with a reviewer attached.
+ */
+@AnalyzeClasses(
+ packages = "dev.caskeleton.bootstrap",
+ importOptions = ProductionClassImportOption.class)
+class StartupFailureContractArchitectureTest {
+
+ /**
+ * {@code TracingSettings#sampleRateValue()} — an API precondition. Blank means "defer to the
+ * per-profile default"; calling the accessor anyway is a bug in the caller, not in configuration.
+ */
+ private static final String TRACING_SETTINGS =
+ "dev.caskeleton.bootstrap.autoconfigure.tracing.TracingSettings";
+
+ /**
+ * {@code HttpClientEnvironmentKeys} — an internal invariant. Its unreachable branch says the
+ * environment-variable template cannot express a list nested three deep; no settings record nests
+ * that far, and a deployment cannot cause it.
+ */
+ private static final String HTTP_CLIENT_ENVIRONMENT_KEYS =
+ "dev.caskeleton.bootstrap.autoconfigure.httpclient.HttpClientEnvironmentKeys";
+
+ /**
+ * {@code RedisKeys} — two JCE invariants ("HmacSHA256 must be available", "a Mac must be
+ * cloneable"). A JVM without HmacSHA256 is not a misconfigured deployment.
+ *
+ *
This entry named {@code RedisCapabilityConfig} until the Redis capability was split by
+ * concern (BOOT-007) and the key digest moved to a type of its own. The rule caught the move,
+ * which is the behaviour to want from it: an allowlist that silently followed a class around
+ * would stop being a statement about which code is exempt.
+ */
+ private static final String REDIS_KEYS = "dev.caskeleton.bootstrap.autoconfigure.redis.RedisKeys";
+
+ /**
+ * {@code StartupFailures} itself — it constructs the typed exceptions this rule exists to
+ * require.
+ */
+ private static final String STARTUP_FAILURES =
+ "dev.caskeleton.bootstrap.runtime.startup.StartupFailures";
+
+ /**
+ * {@code StartupFailureException} — the typed base class. Its constructor delegates to {@code
+ * IllegalStateException(String, Throwable)}, which is the whole point: the typed exceptions stay
+ * source-compatible with the {@code IllegalStateException} the startup path used to throw, so
+ * every existing {@code assertThatThrownBy(...).isInstanceOf(IllegalStateException.class)} kept
+ * passing across this migration.
+ */
+ private static final String STARTUP_FAILURE_EXCEPTION =
+ "dev.caskeleton.bootstrap.runtime.startup.StartupFailureException";
+
+ /**
+ * Matched by prefix rather than exact name, so a nested type is covered by its outer class's
+ * entry.
+ *
+ *
Not cosmetic: the first version of this rule compared exact fully-qualified names and
+ * therefore did not cover {@code RedisCapabilityConfig$KeyDigest}, where two of the three
+ * allowlisted Redis throws actually live. The rule fired on them, and the obvious fix — adding
+ * the nested name too — would have had to be repeated for every future nested type. A prefix
+ * keeps the allowlist to one line per class while still naming that class explicitly.
+ */
+ private static final DescribedPredicate ALLOWLISTED =
+ new DescribedPredicate<>("allowlisted from the StartupFailures contract") {
+ private final List prefixes =
+ List.of(
+ TRACING_SETTINGS,
+ HTTP_CLIENT_ENVIRONMENT_KEYS,
+ REDIS_KEYS,
+ STARTUP_FAILURES,
+ STARTUP_FAILURE_EXCEPTION);
+
+ @Override
+ public boolean test(JavaClass candidate) {
+ String name = candidate.getFullName();
+ return prefixes.stream()
+ .anyMatch(prefix -> name.equals(prefix) || name.startsWith(prefix + "$"));
+ }
+ };
+
+ @ArchTest
+ static final ArchRule STARTUP_FAILURES_ARE_RAISED_THROUGH_THE_TYPED_FACTORY =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton.bootstrap..")
+ .and(DescribedPredicate.not(ALLOWLISTED))
+ .should()
+ .callConstructor(IllegalStateException.class, String.class)
+ .orShould()
+ .callConstructor(IllegalStateException.class, String.class, Throwable.class)
+ .as(
+ "BOOT-003: a startup failure in the composition root must be raised through "
+ + "StartupFailures (envValidation / profileMismatch / requiredAdapterDisabled / "
+ + "migrationFailed), so it carries a sysexits exit code, emits the structured "
+ + "startup.phase/error.code log, and is recognised by "
+ + "StartupFailureExceptionReporter. A raw IllegalStateException gets exit 1, no "
+ + "error code, and Spring Boot's generic stacktrace.")
+ .allowEmptyShould(true);
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java
index c67b583d..48624ba6 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java
@@ -3,6 +3,7 @@ package dev.caskeleton.bootstrap.architecture;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
import static org.assertj.core.api.Assertions.assertThat;
+import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.lang.ArchRule;
@@ -47,6 +48,16 @@ class TestTaxonomyArchitectureTest {
private static final JavaClasses ARCHITECTURE_TESTS =
new ClassFileImporter().importPackages("dev.caskeleton.bootstrap.architecture");
+ /**
+ * Everything under {@code dev.caskeleton.bootstrap} the {@code test} JVM can see — production
+ * classes plus the whole {@code test} source set, and deliberately not the {@code
+ * integrationTest} source set, whose output is absent from the {@code test} runtime classpath.
+ * That absence is what makes this corpus a source-set boundary check rather than another package
+ * check (BOOT-014).
+ */
+ private static final JavaClasses TEST_JVM_CORPUS =
+ new ClassFileImporter().importPackages("dev.caskeleton.bootstrap");
+
// -------------------------------------------------------------------------
// Positive-control / over-block corpora — deterministic class-literal imports.
// -------------------------------------------------------------------------
@@ -147,6 +158,117 @@ class TestTaxonomyArchitectureTest {
.isTrue();
}
+ // -------------------------------------------------------------------------
+ // BOOT-014 — the source-set boundary, not merely the package name
+ // -------------------------------------------------------------------------
+
+ /**
+ * ArchUnit rule: nothing reachable from the {@code test} source set may depend on Testcontainers.
+ *
+ *
The two rules above are scoped to the {@code ..contract..} and {@code ..architecture..}
+ * packages, so they constrain where a Testcontainers test may be *named* while saying nothing
+ * about which Gradle source set compiles it. That is exactly how {@code
+ * ..bootstrap.integration..} — nine Testcontainers classes, four of them with no
+ * {@code @EnabledIf} guard — sat inside {@code src/test} and made {@code ./gradlew
+ * :app-bootstrap:test} require a Docker daemon.
+ *
+ *
This rule closes that gap by constraining the corpus rather than a package: it evaluates
+ * everything under {@code dev.caskeleton.bootstrap} that the {@code test} JVM can see. The {@code
+ * integrationTest} source set's output is not on the {@code test} runtime classpath, so a class
+ * that moves back into {@code src/test} becomes visible here and fails, while the integration
+ * lane itself is invisible and unaffected.
+ *
+ *
{@code ..taxonomyfixtures..} is excluded because {@link
+ * dev.caskeleton.bootstrap.taxonomyfixtures.TestcontainersUsingFixture} carries its
+ * Testcontainers dependency deliberately, as the positive control proving the ban rules fire.
+ * Excluding it by package keeps the exception visible and narrow; the fixture's own non-vacuity
+ * test still asserts the dependency is really there.
+ */
+ static final ArchRule TEST_SOURCE_SET_DOES_NOT_DEPEND_ON_TESTCONTAINERS =
+ noClasses()
+ .that()
+ .resideOutsideOfPackage("..taxonomyfixtures..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("org.testcontainers..")
+ .as(
+ "BOOT-014: the `test` source set must not depend on Testcontainers — "
+ + "`./gradlew :app-bootstrap:test` must not require a Docker daemon. "
+ + "Real-service tests belong to src/integrationTest (the `integrationTest` lane).")
+ .allowEmptyShould(true);
+
+ @Test
+ void testSourceSetHasNoTestcontainersDependency() {
+ // Non-vacuity guard: an empty corpus would make this clean-check pass without checking
+ // anything.
+ assertThat(TEST_JVM_CORPUS.size())
+ .as(
+ "test-JVM corpus (dev.caskeleton.bootstrap) must be non-empty — an empty scan would "
+ + "make this clean-check pass vacuously (BOOT-014)")
+ .isGreaterThan(0);
+
+ EvaluationResult result =
+ TEST_SOURCE_SET_DOES_NOT_DEPEND_ON_TESTCONTAINERS.evaluate(TEST_JVM_CORPUS);
+
+ assertThat(result.hasViolation())
+ .as(
+ "TEST_SOURCE_SET_DOES_NOT_DEPEND_ON_TESTCONTAINERS must NOT fire on the test source "
+ + "set. If it did, a Testcontainers-backed test was added to src/test instead of "
+ + "src/integrationTest — move it, do not widen this rule (BOOT-014). Violations: "
+ + result.getFailureReport())
+ .isFalse();
+ }
+
+ /**
+ * The boundary rule with its fixture exclusion removed.
+ *
+ *
Exists only as the non-vacuity instrument for {@link
+ * #testSourceSetBoundaryIsNonVacuousAndItsExclusionIsLoadBearing()}. A positive control for the
+ * boundary rule cannot be a new Testcontainers-using fixture, because the clean-check above scans
+ * every package except {@code ..taxonomyfixtures..} and would flag it — the control would break
+ * the very rule it exists to prove. Dropping the exclusion instead turns the fixture that is
+ * already in the corpus into the control.
+ */
+ private static final ArchRule TESTCONTAINERS_BAN_WITHOUT_FIXTURE_EXCLUSION =
+ noClasses()
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("org.testcontainers..")
+ .allowEmptyShould(true);
+
+ @Test
+ void testSourceSetBoundaryIsNonVacuousAndItsExclusionIsLoadBearing() {
+ // Detection proof. Without the ..taxonomyfixtures.. exclusion the identical predicate fires on
+ // this corpus, because TestcontainersUsingFixture is in it. So the clean-check passing above
+ // means the rule looked and found nothing — not that it was unable to look.
+ assertThat(
+ TESTCONTAINERS_BAN_WITHOUT_FIXTURE_EXCLUSION.evaluate(TEST_JVM_CORPUS).hasViolation())
+ .as(
+ "the boundary rule's predicate must fire on the deliberate fixture once the "
+ + "..taxonomyfixtures.. exclusion is removed — otherwise the clean-check is "
+ + "vacuous and the exclusion is not load-bearing (BOOT-014)")
+ .isTrue();
+
+ // Scope proof. The corpus must reach test packages the two older package-scoped rules never
+ // cover, or this rule adds nothing over them and BOOT-014 could recur outside their reach.
+ assertThat(corpusCoversPackage(TEST_JVM_CORPUS, "dev.caskeleton.bootstrap.runtime"))
+ .as(
+ "test-JVM corpus must include ..bootstrap.runtime.. — a corpus limited to contract and "
+ + "architecture would duplicate the existing rules instead of closing the "
+ + "source-set gap (BOOT-014)")
+ .isTrue();
+ }
+
+ /** Reports whether the corpus holds at least one class in the given package tree. */
+ private static boolean corpusCoversPackage(JavaClasses corpus, String packagePrefix) {
+ for (JavaClass candidate : corpus) {
+ if (candidate.getPackageName().startsWith(packagePrefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
// -------------------------------------------------------------------------
// Task 3 — D7 / SB-SLICE-C2: Spring slice annotation mixing ban
// -------------------------------------------------------------------------
@@ -203,11 +325,11 @@ class TestTaxonomyArchitectureTest {
@Test
void fixtureLeakRuleFiresOnProductionDependingOnFixture() {
- // Evaluates the @ArchTest rule from CleanArchitectureTest against a deterministic
+ // Evaluates the @ArchTest rule from TypeBoundaryArchitectureTest against a deterministic
// corpus: LeakyProductionConsumerFixture (outside ..fixtures..) depends on
// LeakedTestFixture (in ..fixtures..). Proves the production-side guard fires.
EvaluationResult result =
- CleanArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES.evaluate(
+ TypeBoundaryArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES.evaluate(
FIXTURE_LEAK_VIOLATION_CORPUS);
assertThat(result.hasViolation())
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java
new file mode 100644
index 00000000..4177adf6
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java
@@ -0,0 +1,532 @@
+package dev.caskeleton.bootstrap.architecture;
+
+import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
+import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
+
+import com.tngtech.archunit.base.DescribedPredicate;
+import com.tngtech.archunit.core.domain.JavaAnnotation;
+import com.tngtech.archunit.core.domain.JavaClass;
+import com.tngtech.archunit.core.domain.JavaField;
+import com.tngtech.archunit.core.domain.JavaMethod;
+import com.tngtech.archunit.junit.AnalyzeClasses;
+import com.tngtech.archunit.junit.ArchTest;
+import com.tngtech.archunit.lang.ArchCondition;
+import com.tngtech.archunit.lang.ArchRule;
+import com.tngtech.archunit.lang.ConditionEvents;
+import com.tngtech.archunit.lang.SimpleConditionEvent;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Whether a type escapes the layer that owns it. Dependency rules say which package may reference
+ * which; these say which types may appear in a signature, which is the leak that a
+ * dependency check cannot see — an allowed edge carrying a forbidden type across it.
+ *
+ *
타입이 자기 계층 밖으로 새지 않는가 — 엔티티, DTO, 도메인 타입의 이동 금지.
+ *
+ *
{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by
+ * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields
+ * inside a nested class — the rules would have stopped running while the build stayed green
+ * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share
+ * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest}
+ * holds the full rule inventory so none can be lost in a move like this one.
+ */
+@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class)
+class TypeBoundaryArchitectureTest {
+
+ /** A persistence entity is one the ORM maps, wherever it lives. */
+ private static final DescribedPredicate ARE_PERSISTENCE_ENTITIES =
+ new DescribedPredicate<>("annotated as a persistence entity") {
+ @Override
+ public boolean test(JavaClass candidate) {
+ return candidate.isAnnotatedWith("jakarta.persistence.Entity")
+ || candidate.isAnnotatedWith("jakarta.persistence.MappedSuperclass")
+ || candidate.isAnnotatedWith("jakarta.persistence.Embeddable")
+ || candidate.isAnnotatedWith(
+ "org.springframework.data.mongodb.core.mapping.Document");
+ }
+ };
+
+ /** A mapper is a translation type, identified by its name rather than its directory. */
+ private static final DescribedPredicate ARE_PERSISTENCE_MAPPERS =
+ new DescribedPredicate<>("a persistence mapper by name") {
+ @Override
+ public boolean test(JavaClass candidate) {
+ return candidate.getSimpleName().endsWith("Mapper")
+ && candidate.getPackageName().contains(".adapter.outbound.persistence");
+ }
+ };
+
+ @ArchTest
+ static final ArchRule PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER =
+ classes()
+ .that(ARE_PERSISTENCE_ENTITIES)
+ .should()
+ .resideInAnyPackage(
+ "..adapter.outbound.persistence..",
+ "..sample.portfolio.adapter.outbound.persistence..")
+ .as(
+ "a type the ORM maps belongs to a persistence adapter whatever it is called and "
+ + "wherever it is filed (NTF-022 semantic selection)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule WEB_DTOS_STAY_IN_WEB_ADAPTER =
+ classes()
+ .that()
+ .resideInAPackage("..adapter.inbound.web..dto..")
+ .should()
+ .onlyBeAccessed()
+ .byClassesThat()
+ .resideInAnyPackage("..adapter.inbound.web..")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES =
+ noClasses()
+ .that(ARE_PERSISTENCE_MAPPERS)
+ .should()
+ .dependOnClassesThat()
+ .resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO =
+ noClasses()
+ .that()
+ .resideOutsideOfPackage("..sample.portfolio..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("..sample.portfolio..");
+
+ @ArchTest
+ static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES =
+ noClasses()
+ .that()
+ .resideInAPackage("dev.caskeleton..")
+ .and()
+ .resideOutsideOfPackage("..fixtures..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("..fixtures..")
+ .as(
+ "feature-test-taxonomy-fixture-contract D6: production code must never depend on a"
+ + " test fixture — fixtures live in src/test/.../fixtures/ (test-only); this"
+ + " guards against a fixture leaking onto the main classpath.")
+ .allowEmptyShould(true);
+
+ // ---- feature-boundary-validation-mapping-contract ----
+
+ @ArchTest
+ static final ArchRule REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS =
+ classes()
+ .that()
+ .resideInAPackage("..adapter.inbound.web..dto..")
+ .should(notBeAnnotatedWithJsonIgnoreUnknown())
+ .as(
+ "class-level @JsonIgnoreProperties(ignoreUnknown = true) on a request DTO "
+ + "silently swallows extra fields and defeats the B1 fail-fast boundary "
+ + "(feature-boundary-validation-mapping-contract B1)")
+ .allowEmptyShould(true);
+
+ // ---- feature-boundary-validation-mapping-contract — 판정 기준 §Forbidden + 테스트 계약 ----
+
+ @ArchTest
+ static final ArchRule CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES =
+ methods()
+ .that()
+ .areDeclaredInClassesThat(CleanArchitectureRuleSupport.ARE_CONTROLLERS)
+ .and()
+ .arePublic()
+ .should()
+ .notHaveRawReturnType(
+ JavaClass.Predicates.resideInAnyPackage(
+ "..domain..",
+ "..adapter.outbound.persistence.entity..",
+ "..adapter.outbound.persistence.repository.."))
+ .as(
+ "controller methods must return DTO / record / Envelope / ResponseEntity — "
+ + "never a domain entity, JPA entity, or repository type "
+ + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)")
+ .allowEmptyShould(true);
+
+ @ArchTest
+ static final ArchRule OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES =
+ methods()
+ .that()
+ .areDeclaredInClassesThat(
+ JavaClass.Predicates.resideInAPackage("..adapter.outbound..")
+ .and(implementsApplicationLayerPort()))
+ // @Configuration factory classes are excluded: a @Bean method legitimately
+ // returns the adapter's own outbound port type for DI wiring (e.g.
+ // MessagingConfig#messagePublisher -> MessagePublisher) — that is
+ // composition-root assembly, not an adapter *response* method leaking an
+ // external type. B7 targets the latter (ACL leakage), not DI factories
+ // (feature-integration-adapter-templates Layer 1 @ConditionalOnProperty configs).
+ .and()
+ .areDeclaredInClassesThat()
+ .areNotAnnotatedWith("org.springframework.context.annotation.Configuration")
+ // @ConfigurationProperties settings holders are excluded for the same reason as
+ // @Configuration factories: they are config-binding types, not adapter *response*
+ // surfaces. Their accessors return bound config values (primitives, Duration,
+ // DataSize) and nested config records (e.g. OutboundHttpSettings#retry ->
+ // OutboundHttpSettings.Retry) — internal settings, not external HTTP / messaging
+ // responses, so B7's ACL-leakage target does not apply.
+ .and()
+ .areDeclaredInClassesThat()
+ .areNotAnnotatedWith(
+ "org.springframework.boot.context.properties.ConfigurationProperties")
+ // The canonical HTTP control plane and explicit legacy factories have a small exact
+ // method whitelist below. Names, packages, and arbitrary @Bean annotations do not bypass
+ // B7.
+ .and(notAnExactHttpControlPlaneMethod())
+ // Redis provider binding and secret material provider SPIs likewise expose only a small
+ // exact set of typed composition accessors. Command/router/native runtime and secret
+ // value accessors are deliberately not exempt.
+ .and(notAnExactRedisCompositionMethod())
+ .and()
+ // Package-private implementation types cannot expose their methods outside the
+ // adapter package. B7 protects the externally reachable adapter API, not internal
+ // records and fault-injection seams used to implement that API.
+ .areDeclaredInClassesThat()
+ .arePublic()
+ .and()
+ .arePublic()
+ .and()
+ .areNotStatic()
+ .should()
+ .notHaveRawReturnType(
+ JavaClass.Predicates.resideInAnyPackage(
+ "..adapter.outbound..",
+ "..adapter.inbound.web..",
+ "..adapter.outbound.persistence.."))
+ .as(
+ "B7: outbound application-port adapter public methods must return domain types"
+ + " (or primitives/wrappers/Optional) — raw external response types must not"
+ + " escape the adapter package (feature-boundary-validation-mapping-contract B7"
+ + " ACL). @Configuration @Bean factory methods and @ConfigurationProperties"
+ + " settings holders are excluded — they assemble port bindings / bind config,"
+ + " not adapter response surfaces.")
+ .allowEmptyShould(true);
+
+ private static final Set B7_EXACT_HTTP_CONTROL_PLANE_METHODS =
+ // The HTTP Client Platform (feature-httpclient-platform) exposes no outbound adapter
+ // class that implements an application-layer port, so B7 has no HTTP control-plane
+ // method to exempt. The whitelist mechanism stays in place so a future application-port
+ // implementation must justify each exact accessor rather than widen the rule.
+ Set.of();
+
+ private static DescribedPredicate notAnExactHttpControlPlaneMethod() {
+ return new DescribedPredicate<>("not an exact HTTP control-plane accessor/factory") {
+ @Override
+ public boolean test(JavaMethod method) {
+ return !B7_EXACT_HTTP_CONTROL_PLANE_METHODS.contains(ExactMethodSignature.from(method));
+ }
+ };
+ }
+
+ private static final Set B7_EXACT_REDIS_COMPOSITION_METHODS =
+ Set.of(
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings",
+ "dataAuthentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings",
+ "dataTls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone",
+ "dataAuthentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone",
+ "dataTls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
+ "sentinelAuthentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
+ "sentinelTls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
+ "dataAuthentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel",
+ "dataTls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster",
+ "dataAuthentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster",
+ "dataTls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
+ "topology",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$Topology"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
+ "standalone",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$StandaloneProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
+ "sentinel",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
+ "cluster",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$ClusterProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
+ "authentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties",
+ "tls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties",
+ "authentication",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties",
+ "tls",
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$RuntimeProperties",
+ "clientSettings",
+ "dev.caskeleton.adapter.outbound.cache.redis.runtime.RedisClientRuntimeSettings"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider",
+ "resolve",
+ List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"),
+ "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial"),
+ signature(
+ "dev.caskeleton.adapter.outbound.cache.redis.security.RedisTrustMaterialProvider",
+ "resolve",
+ List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"),
+ "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisTrustMaterial"));
+
+ private static DescribedPredicate notAnExactRedisCompositionMethod() {
+ return new DescribedPredicate<>("not an exact Redis composition accessor or material SPI") {
+ @Override
+ public boolean test(JavaMethod method) {
+ return !B7_EXACT_REDIS_COMPOSITION_METHODS.contains(ExactMethodSignature.from(method));
+ }
+ };
+ }
+
+ private static ExactMethodSignature signature(String owner, String name, String returnType) {
+ return signature(owner, name, List.of(), returnType);
+ }
+
+ private static ExactMethodSignature signature(
+ String owner, String name, List parameters, String returnType) {
+ return new ExactMethodSignature(owner, name, parameters, returnType);
+ }
+
+ private record ExactMethodSignature(
+ String owner, String name, List parameters, String returnType) {
+
+ private ExactMethodSignature {
+ parameters = List.copyOf(parameters);
+ }
+
+ private static ExactMethodSignature from(JavaMethod method) {
+ List parameterTypes =
+ method.getRawParameterTypes().stream().map(JavaClass::getFullName).toList();
+ return new ExactMethodSignature(
+ method.getOwner().getFullName(),
+ method.getName(),
+ parameterTypes,
+ method.getRawReturnType().getFullName());
+ }
+ }
+
+ private static DescribedPredicate implementsApplicationLayerPort() {
+ return DescribedPredicate.describe(
+ "implement an application-layer port",
+ type ->
+ type.getAllRawInterfaces().stream()
+ .anyMatch(
+ iface ->
+ iface.getPackageName().contains(".application.")
+ || iface.getPackageName().endsWith(".application")));
+ }
+
+ @ArchTest
+ static final ArchRule VALID_CASCADE_DEPTH_AT_MOST_THREE =
+ classes()
+ .that()
+ .resideInAPackage("..adapter.inbound.web..dto..")
+ .should(notHaveValidCascadeDeeperThan(3))
+ .as(
+ "B4: @Valid cascade depth must be ≤ 3 to bound nested-DTO recursion cost "
+ + "(feature-boundary-validation-mapping-contract B4 — DoS defense)")
+ .allowEmptyShould(true);
+
+ // ---- feature-business-rule-validation-contract C1/D1 (validation stays at the web boundary)
+ // ----
+
+ @ArchTest
+ static final ArchRule VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY =
+ noClasses()
+ .that()
+ .resideInAnyPackage("..domain..", "..application..")
+ .should()
+ .dependOnClassesThat()
+ .resideInAPackage("jakarta.validation..")
+ .as(
+ "C1/D1: Bean Validation (jakarta.validation @NotNull / @Valid / ...) is the "
+ + "syntax layer and must stay at the adapter-web request boundary. Domain "
+ + "invariants live in domain model / value-object constructors and application "
+ + "policy is plain Java — neither is expressed with annotation-driven "
+ + "validation (feature-business-rule-validation-contract C1/D1, boundary B4 "
+ + "layering). Production code is scanned only — fixtures in "
+ + "..architecture.violations.. are out of scope under "
+ + "ImportOption.DoNotIncludeTests.")
+ .allowEmptyShould(true);
+
+ private static ArchCondition notHaveValidCascadeDeeperThan(int maxDepth) {
+ return new ArchCondition<>("not have @Valid cascade deeper than " + maxDepth + " nested DTOs") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ int depth = computeValidDepth(item, new HashSet<>(), 0);
+ if (depth > maxDepth) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " has @Valid cascade depth "
+ + depth
+ + " (max allowed: "
+ + maxDepth
+ + ")"));
+ }
+ }
+
+ private int computeValidDepth(JavaClass type, Set visited, int currentDepth) {
+ if (!visited.add(type.getName())) {
+ return currentDepth;
+ }
+ int maxFound = currentDepth;
+ for (JavaField field : type.getFields()) {
+ boolean hasValid = false;
+ for (JavaAnnotation> a : field.getAnnotations()) {
+ if (a.getRawType().getName().equals("jakarta.validation.Valid")) {
+ hasValid = true;
+ break;
+ }
+ }
+ if (hasValid) {
+ JavaClass fieldType = field.getRawType();
+ if (fieldType.getPackageName().startsWith("dev.caskeleton")) {
+ int sub = computeValidDepth(fieldType, visited, currentDepth + 1);
+ if (sub > maxFound) {
+ maxFound = sub;
+ }
+ }
+ }
+ }
+ return maxFound;
+ }
+ };
+ }
+
+ private static ArchCondition notBeAnnotatedWithJsonIgnoreUnknown() {
+ return new ArchCondition<>(
+ "not be annotated with @JsonIgnoreProperties(ignoreUnknown = true)") {
+ @Override
+ public void check(JavaClass item, ConditionEvents events) {
+ item.tryGetAnnotationOfType("com.fasterxml.jackson.annotation.JsonIgnoreProperties")
+ .ifPresent(
+ annotation -> {
+ Object ignoreUnknown = annotation.get("ignoreUnknown").orElse(null);
+ if (Boolean.TRUE.equals(ignoreUnknown)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ item,
+ "Class "
+ + item.getName()
+ + " is annotated with "
+ + "@JsonIgnoreProperties(ignoreUnknown = true) — "
+ + "use field-level @JsonIgnore on the specific "
+ + "field instead (B1)"));
+ }
+ });
+ }
+ };
+ }
+
+ // ---- feature-application-query-bypass-contract D1 (read/query port purity guardrail) ----
+
+ @ArchTest
+ static final ArchRule QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES =
+ methods()
+ .that()
+ .areDeclaredInClassesThat(
+ JavaClass.Predicates.resideInAPackage("..application..")
+ .and(JavaClass.Predicates.simpleNameEndingWith("QueryPort")))
+ .and()
+ .arePublic()
+ .should(
+ notLeakDomainJpaOrWebThroughReturnType(
+ "..domain..",
+ "..adapter..",
+ "jakarta.persistence..",
+ "javax.persistence..",
+ "org.springframework.web..",
+ "org.hibernate.."))
+ .as(
+ "D1 QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES: application read/query ports"
+ + " (classes whose simple name ends with 'QueryPort') must return"
+ + " application-layer projection DTOs — never a domain aggregate, JPA entity, or"
+ + " web type, INCLUDING through generic type arguments like List"
+ + " (checked via JavaType.getAllInvolvedRawTypes(), since a raw-return-type check"
+ + " alone misses generic leakage) (feature-application-query-bypass-contract D1"
+ + " core purity guardrail). The through-aggregate read path (repository ports"
+ + " returning the domain aggregate) is a separate, equally-valid choice and is"
+ + " intentionally out of this rule's scope.")
+ .allowEmptyShould(true);
+
+ private static ArchCondition notLeakDomainJpaOrWebThroughReturnType(
+ String... forbiddenPackages) {
+ DescribedPredicate forbidden =
+ JavaClass.Predicates.resideInAnyPackage(forbiddenPackages);
+ return new ArchCondition<>(
+ "return application projection DTOs only "
+ + "(no domain / adapter / JPA / web types, including generic type arguments)") {
+ @Override
+ public void check(JavaMethod method, ConditionEvents events) {
+ // getAllInvolvedRawTypes() flattens the return type plus every generic type
+ // argument (recursively) to their raw classes — so List surfaces
+ // both java.util.List AND DomainType, which a raw-return-type predicate misses.
+ for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) {
+ if (forbidden.test(involved)) {
+ events.add(
+ SimpleConditionEvent.violated(
+ method,
+ "Method "
+ + method.getFullName()
+ + " leaks "
+ + involved.getName()
+ + " through its return type (directly or as a generic type argument)"
+ + " — a *QueryPort must return application-layer projection DTOs "
+ + "(feature-application-query-bypass-contract D1)"));
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java
index 72b6132e..ecc3b33d 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java
@@ -3,8 +3,8 @@
* dev.caskeleton.bootstrap.architecture.ArchitectureViolationFixtureTest}.
*
*
Each class in this package is an intentional violation of exactly one ArchUnit rule declared
- * in {@link dev.caskeleton.bootstrap.architecture.CleanArchitectureTest}. The fixtures live under
- * {@code src/test/java/...}, so the main {@code @AnalyzeClasses(importOptions =
+ * in the architecture suites in {@code dev.caskeleton.bootstrap.architecture}. The fixtures live
+ * under {@code src/test/java/...}, so the main {@code @AnalyzeClasses(importOptions =
* DoNotIncludeTests.class)} excludes them from the production architecture suite — they never
* trigger a "real" violation.
*
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderCompositionTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderCompositionTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderCompositionTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderCompositionTest.java
index 01b3ae95..3832799a 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderCompositionTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderCompositionTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderScanCollisionTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderScanCollisionTest.java
index 9b68344c..de75e828 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderScanCollisionTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettingsTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettingsTest.java
similarity index 95%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettingsTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettingsTest.java
index d35a110d..ab9e5160 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettingsTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettingsTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.idempotency;
+package dev.caskeleton.bootstrap.autoconfigure.idempotency;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/CapabilityEntityScanRegistrationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/CapabilityEntityScanRegistrationTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/CapabilityEntityScanRegistrationTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/CapabilityEntityScanRegistrationTest.java
index c3350c2e..15c3dbe2 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/CapabilityEntityScanRegistrationTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/CapabilityEntityScanRegistrationTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.autoconfigure.persistencejpa;
+package dev.caskeleton.bootstrap.autoconfigure.jpa;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPortTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPortTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPortTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPortTest.java
index dd5afa49..5c42324d 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPortTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPortTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.lock;
+package dev.caskeleton.bootstrap.autoconfigure.lock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributorTest.java
similarity index 95%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributorTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributorTest.java
index 9684a947..ac3974ad 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributorTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributorTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.mongo;
+package dev.caskeleton.bootstrap.autoconfigure.mongo;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationAutoConfigurationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationAutoConfigurationTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationAutoConfigurationTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationAutoConfigurationTest.java
index 1a979f33..f7d207d3 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationAutoConfigurationTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationAutoConfigurationTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationLegacyNamespaceRetirementTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationLegacyNamespaceRetirementTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationLegacyNamespaceRetirementTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationLegacyNamespaceRetirementTest.java
index 73f00361..7dcd6f32 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationLegacyNamespaceRetirementTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationLegacyNamespaceRetirementTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationModeSsotTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationModeSsotTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationModeSsotTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationModeSsotTest.java
index 00250205..8bcb1ca2 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationModeSsotTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationModeSsotTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPayloadAtRestContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPayloadAtRestContractTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPayloadAtRestContractTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPayloadAtRestContractTest.java
index bb363099..84aa5d55 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPayloadAtRestContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPayloadAtRestContractTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPlatformReachabilityTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformReachabilityTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPlatformReachabilityTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformReachabilityTest.java
index 4b2ecbcf..085df98a 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPlatformReachabilityTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformReachabilityTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationSecretsValidationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationSecretsValidationTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationSecretsValidationTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationSecretsValidationTest.java
index ee85f7bd..b2e5ddfd 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationSecretsValidationTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationSecretsValidationTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
@@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformMode;
import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings;
import dev.caskeleton.application.notification.platform.security.SecretPurpose;
-import dev.caskeleton.bootstrap.notification.NotificationPlatformSecretsConfig.NotificationSecretsSettings;
+import dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformSecretsConfig.NotificationSecretsSettings;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.Map;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationWorkerLifecycleTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationWorkerLifecycleTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationWorkerLifecycleTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationWorkerLifecycleTest.java
index 49c7c0a3..371e1432 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationWorkerLifecycleTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationWorkerLifecycleTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification;
+package dev.caskeleton.bootstrap.autoconfigure.notification;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationTest.java
index 8f17cacf..bc45382e 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.notification.observation;
+package dev.caskeleton.bootstrap.autoconfigure.notification.observation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderAttemptLimiter;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderRuntime;
import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderRuntimeRegistry;
+import dev.caskeleton.adapter.outbound.notification.platform.observation.MicrometerNotificationMetrics;
import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter;
import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationServingThresholds;
import dev.caskeleton.application.notification.platform.api.ProviderId;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfigTest.java
similarity index 87%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfigTest.java
index c88efe12..c6637f8d 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfigTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidatorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidatorTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidatorTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidatorTest.java
index 96abe8c7..1043be33 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidatorTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidatorTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxSettingsTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettingsTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxSettingsTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettingsTest.java
index 7fbde97d..5ea26086 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxSettingsTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettingsTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.outbox;
+package dev.caskeleton.bootstrap.autoconfigure.outbox;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidatorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidatorTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidatorTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidatorTest.java
index 987b8100..f3699174 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidatorTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidatorTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.runtime;
+package dev.caskeleton.bootstrap.autoconfigure.redis;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityCompositionTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityCompositionTest.java
index e76b2832..d1e8c906 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityCompositionTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.redis;
+package dev.caskeleton.bootstrap.autoconfigure.redis;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessorTest.java
similarity index 95%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessorTest.java
index 935f6fb7..352441f7 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessorTest.java
@@ -1,7 +1,9 @@
-package dev.caskeleton.bootstrap.runtime;
+package dev.caskeleton.bootstrap.autoconfigure.redis;
import static org.assertj.core.api.Assertions.assertThat;
+import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration;
+import dev.caskeleton.bootstrap.runtime.ReadinessGroup;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -11,7 +13,6 @@ import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
-import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroup;
import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroups;
import org.springframework.boot.health.autoconfigure.actuate.endpoint.HealthEndpointAutoConfiguration;
@@ -25,7 +26,6 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
-import org.springframework.core.io.ClassPathResource;
/**
* Starts a real Actuator health context in each of the three deployment shapes.
@@ -51,11 +51,9 @@ class RedisReadinessGroupPostProcessorTest {
*/
private static String shippedReadinessInclude() throws IOException {
MutablePropertySources sources = new MutablePropertySources();
- new YamlPropertySourceLoader()
- .load("application", new ClassPathResource("application.yml"))
- .forEach(sources::addLast);
+ ShippedConfiguration.propertySources().forEach(sources::addLast);
return new PropertySourcesPropertyResolver(sources)
- .getProperty(RedisReadinessGroupPostProcessor.READINESS_INCLUDE);
+ .getProperty(ReadinessGroup.INCLUDE_PROPERTY);
}
private static ConfigurableApplicationContext boot(Map properties)
@@ -71,7 +69,7 @@ class RedisReadinessGroupPostProcessorTest {
// The setting that turned a naming mistake into a startup failure. On, exactly as in
// production.
defaults.put("management.endpoint.health.validate-group-membership", "true");
- defaults.put(RedisReadinessGroupPostProcessor.READINESS_INCLUDE, shippedReadinessInclude());
+ defaults.put(ReadinessGroup.INCLUDE_PROPERTY, shippedReadinessInclude());
defaults.putAll(properties);
return new SpringApplicationBuilder(HealthContext.class)
.web(WebApplicationType.NONE)
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorderTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorderTest.java
similarity index 96%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorderTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorderTest.java
index 6a5007c4..1a6b4a01 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorderTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorderTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingActivationContextTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingActivationContextTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingActivationContextTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingActivationContextTest.java
index 002ffa31..60307e98 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingActivationContextTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingActivationContextTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolverTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolverTest.java
similarity index 98%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolverTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolverTest.java
index 9de7ce05..208f2689 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolverTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolverTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessorTest.java
similarity index 96%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessorTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessorTest.java
index 584e5966..53980fb4 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessorTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessorTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import static org.assertj.core.api.Assertions.assertThat;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSettingsTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettingsTest.java
similarity index 99%
rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSettingsTest.java
rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettingsTest.java
index 2309e2af..77b89de5 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSettingsTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettingsTest.java
@@ -1,4 +1,4 @@
-package dev.caskeleton.bootstrap.tracing;
+package dev.caskeleton.bootstrap.autoconfigure.tracing;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java
new file mode 100644
index 00000000..ecd63b0d
--- /dev/null
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java
@@ -0,0 +1,192 @@
+package dev.caskeleton.bootstrap.contract;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The configuration namespaces this application owns, pinned (BOOT-001).
+ *
+ *
The shipped configuration grew three application-owned roots — {@code ca-skeleton}, {@code
+ * app} and {@code backend} — and nothing recorded why, so "where do I set this?" had three answers
+ * and a reader had to know which. Two capabilities were registered under both of the first two.
+ *
+ *
Why this pins the split instead of removing it
+ *
+ *
The obvious fix — rename everything to one root — is wrong for at least one of the trees, and
+ * the reason is worth stating because it is not visible from the YAML.
+ *
+ *
{@code ca-skeleton.*} keys are populated from {@code APP_*} environment variables through
+ * explicit {@code ${...}} placeholders, so the property name and the variable name are independent:
+ * renaming the property changes nothing an operator types. {@code app.httpclient.*} is different.
+ * Its list-valued settings are bound straight from the environment by relaxed binding, which is why
+ * {@code HttpClientEnvironmentKeys} exists at all and why it derives its templates from {@code
+ * APP_HTTPCLIENT_}. Renaming that prefix to {@code ca-skeleton.httpclient} would rename every
+ * {@code APP_HTTPCLIENT_*} variable to {@code CA_SKELETON_HTTPCLIENT_*} — breaking the repository's
+ * documented convention that application-owned environment uses the {@code APP_} prefix, and every
+ * deployment that sets one.
+ *
+ *
{@code app.messaging.*} is out of reach for a second reason: it belongs to the vendored {@code
+ * messaging:*} platform, which by design depends on no type of this application and owns its own
+ * configuration contract.
+ *
+ *
So the roots stay, and what changes is that they can no longer grow quietly. A fourth root, or
+ * a new prefix under an existing one, fails here and has to be argued for.
+ */
+class ConfigurationNamespaceContractTest {
+
+ /**
+ * The roots a shipped resource may declare.
+ *
+ *
{@code spring} and {@code management} / {@code server} / {@code logging} / {@code springdoc}
+ * are framework namespaces — this application does not own their shape, only their values.
+ */
+ private static final Set ALLOWED_ROOTS =
+ Set.of(
+ "spring",
+ "management",
+ "server",
+ "logging",
+ "springdoc",
+ "ca-skeleton",
+ "app",
+ "backend");
+
+ /**
+ * Every {@code @ConfigurationProperties} prefix and {@code PREFIX} constant this leaf declares.
+ *
+ *
Listed exhaustively rather than matched by pattern. A pattern would accept a new prefix on
+ * the day it is written, which is the moment the decision is actually being made.
+ */
+ private static final Set DECLARED_PREFIXES =
+ new TreeSet<>(
+ List.of(
+ "app.fileserver-platform",
+ "app.httpclient",
+ "app.jpa-platform",
+ "ca-skeleton.async.executor",
+ "ca-skeleton.bootstrap",
+ "ca-skeleton.capabilities",
+ "ca-skeleton.capabilities.idempotency",
+ "ca-skeleton.domain-context",
+ "ca-skeleton.idempotency",
+ "ca-skeleton.logging",
+ "ca-skeleton.notification.platform.secrets",
+ "ca-skeleton.outbox",
+ "ca-skeleton.privacy",
+ "ca-skeleton.runtime",
+ "ca-skeleton.secret-source",
+ "ca-skeleton.security",
+ "ca-skeleton.tracing"));
+
+ private static final Pattern PREFIX_ANNOTATION =
+ Pattern.compile("@ConfigurationProperties\\(\\s*(?:prefix\\s*=\\s*)?\"([^\"]+)\"");
+
+ private static final Pattern PREFIX_CONSTANT =
+ Pattern.compile("String PREFIX\\s*=\\s*\"([^\"]+)\"");
+
+ private static final Pattern ROOT_KEY = Pattern.compile("(?m)^([a-zA-Z][A-Za-z0-9_-]*):\\s*$");
+
+ @Test
+ @DisplayName("the shipped configuration declares no root beyond the recorded ones")
+ void shippedConfigurationDeclaresOnlyRecordedRoots() {
+ Set roots = new TreeSet<>();
+ Matcher root = ROOT_KEY.matcher(ShippedConfiguration.text());
+ while (root.find()) {
+ roots.add(root.group(1));
+ }
+
+ assertThat(roots)
+ .as(
+ "a fourth application-owned root would give 'where do I set this?' another answer. "
+ + "Add it to ALLOWED_ROOTS only with the reason written down — see this class's "
+ + "Javadoc for why the existing three are not simply merged (BOOT-001).")
+ .isSubsetOf(ALLOWED_ROOTS);
+ assertThat(roots)
+ .as("the scan must actually reach the shipped roots, or this check passes vacuously")
+ .contains("ca-skeleton", "app", "spring");
+ }
+
+ @Test
+ @DisplayName("every declared settings prefix is one this contract records")
+ void everyDeclaredPrefixIsRecorded() throws IOException {
+ Set found = new LinkedHashSet<>();
+ Path main = repositoryRoot().resolve("src/app-bootstrap/src/main/java");
+ try (Stream sources = Files.walk(main)) {
+ for (Path source : sources.filter(p -> p.toString().endsWith(".java")).toList()) {
+ String text = Files.readString(source);
+ for (Pattern pattern : List.of(PREFIX_ANNOTATION, PREFIX_CONSTANT)) {
+ Matcher matcher = pattern.matcher(text);
+ while (matcher.find()) {
+ found.add(matcher.group(1));
+ }
+ }
+ }
+ }
+
+ assertThat(found)
+ .as("the scan must find the leaf's settings prefixes, or this check passes vacuously")
+ .hasSizeGreaterThan(10);
+ assertThat(found)
+ .as(
+ "a settings prefix that is not recorded here is a namespace decision nobody reviewed. "
+ + "Record it, and prefer ca-skeleton.* unless the keys bind directly from the "
+ + "environment — see this class's Javadoc (BOOT-001).")
+ .isSubsetOf(DECLARED_PREFIXES);
+ }
+
+ @Test
+ @DisplayName("each capability's configuration lives in exactly one shipped file")
+ void eachCapabilityHasOneHome() {
+ // The operator-facing half of BOOT-001, and the half the split actually fixed: notification was
+ // registered under both roots, in one 974-line file, 470 lines apart. The two trees are still
+ // two
+ // trees — one is the delivery platform, the other the R0 legacy seam — but they are now in
+ // config/notification.yml together, so looking up "notification" reaches both.
+ assertThat(ShippedConfiguration.resources())
+ .as("the split must be in place for this contract to mean anything")
+ .contains("config/notification.yml");
+
+ String notification = fileText("config/notification.yml");
+ assertThat(notification)
+ .as("the delivery platform tree must be in the notification file")
+ .contains("ca-skeleton:")
+ .contains("platform:");
+ assertThat(notification)
+ .as("the legacy provider-selection tree must be in the same file, not a root away")
+ .contains("app:");
+ }
+
+ private static String fileText(String resource) {
+ String marker = "# --- " + resource + " ---";
+ String all = ShippedConfiguration.text();
+ int start = all.indexOf(marker);
+ assertThat(start).as("%s must be among the shipped resources", resource).isNotNegative();
+ int next = all.indexOf("# --- ", start + marker.length());
+ return next < 0 ? all.substring(start) : all.substring(start, next);
+ }
+
+ private static Path repositoryRoot() {
+ Path candidate = Path.of("").toAbsolutePath();
+ while (candidate != null
+ && !Files.isRegularFile(candidate.resolve("src/config/architecture/modules.json"))) {
+ candidate = candidate.getParent();
+ }
+ if (candidate == null) {
+ throw new IllegalStateException("could not locate the repository root from the test cwd");
+ }
+ return candidate;
+ }
+}
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java
index 133e061f..b38b8495 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java
@@ -5,8 +5,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import dev.caskeleton.adapter.inbound.web.observability.MdcKeys;
import dev.caskeleton.adapter.inbound.web.observability.ResponseMetaFactory;
import dev.caskeleton.bootstrap.async.AsyncContextTaskDecorator;
+import dev.caskeleton.bootstrap.autoconfigure.tracing.TracingSamplingRateGauge;
import dev.caskeleton.bootstrap.contract.support.RepositoryContractResources;
-import dev.caskeleton.bootstrap.tracing.TracingSamplingRateGauge;
import dev.caskeleton.shared.concurrency.DomainContextPropagatorFactory;
import dev.caskeleton.shared.concurrency.DomainContextStrategy;
import dev.caskeleton.shared.response.ResponseMeta;
@@ -37,10 +37,10 @@ import org.yaml.snakeyaml.Yaml;
*
*
{@code contract-verification:trace-propagation} — this class
*
{@code contract-verification:metrics-cardinality} — this class
*
{@code contract-verification:log-mdc-keys} — this class
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ManagementActuatorSecurityContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ManagementActuatorSecurityContractTest.java
index e7d240fe..4038d62d 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ManagementActuatorSecurityContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ManagementActuatorSecurityContractTest.java
@@ -3,9 +3,9 @@ package dev.caskeleton.bootstrap.contract;
import static org.assertj.core.api.Assertions.assertThat;
import dev.caskeleton.bootstrap.contract.support.RepositoryContractResources;
+import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration;
import dev.caskeleton.shared.error.Category;
import dev.caskeleton.shared.error.OperationalError;
-import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -241,21 +241,17 @@ class ManagementActuatorSecurityContractTest {
// ---- helpers ----
/**
- * Loads the real shipped application.yml (main resource, on the test classpath) and returns the
- * {@code management:} subtree.
+ * The {@code management:} subtree of the shipped configuration.
+ *
+ *
Read through {@link ShippedConfiguration} rather than from {@code application.yml}: the
+ * actuator block moved to {@code config/observability.yml} with the split (BOOT-008), and a
+ * security contract that stops finding its subject must fail loudly, not pass vacuously.
*/
private static Map managementSubtree() {
- try (InputStream in =
- Thread.currentThread().getContextClassLoader().getResourceAsStream("application.yml")) {
- assertThat(in).as("application.yml must be on the test classpath").isNotNull();
- Map root = new Yaml().load(in);
- @SuppressWarnings("unchecked")
- Map mgmt = (Map) root.get("management");
- assertThat(mgmt).as("application.yml must declare a `management:` block").isNotNull();
- return mgmt;
- } catch (IOException e) {
- throw new IllegalStateException("failed to read application.yml", e);
- }
+ @SuppressWarnings("unchecked")
+ Map mgmt = (Map) ShippedConfiguration.tree().get("management");
+ assertThat(mgmt).as("the shipped configuration must declare a `management:` block").isNotNull();
+ return mgmt;
}
/** Walks a nested snakeyaml map by key path; returns null if any segment is missing. */
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java
index fbb020e5..a5e452a7 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ProfileSeparationContractTest.java
@@ -2,6 +2,7 @@ package dev.caskeleton.bootstrap.contract;
import static org.assertj.core.api.Assertions.assertThat;
+import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -106,7 +107,9 @@ class ProfileSeparationContractTest {
@Test
void theProfilelessDefaultIsPostgreSqlRatherThanTheInMemoryDatabase() throws IOException {
- Map, ?> base = yaml("application.yml");
+ // The merged shipped configuration; the datasource block moved to config/persistence.yml
+ // when the base file was split (BOOT-008).
+ Map, ?> base = ShippedConfiguration.tree();
assertThat(vendorOf(base))
.as("a deployment that activates no profile must not silently get H2")
@@ -254,9 +257,10 @@ class ProfileSeparationContractTest {
Deque indents = new ArrayDeque<>();
Deque names = new ArrayDeque<>();
- for (String raw :
- Files.readAllLines(
- REPOSITORY_ROOT.resolve("src/app-bootstrap/src/main/resources/application.yml"))) {
+ // Every shipped resource, scanned as one document. The indentation walk below builds key paths
+ // from nesting, and each imported file starts again at its own root key — which is exactly what
+ // the walk already handles, since that is how a root key behaves in the original file too.
+ for (String raw : ShippedConfiguration.text().lines().toList()) {
String line = raw.strip();
if (line.isEmpty() || line.startsWith("#") || !line.contains(":")) {
continue;
diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/messaging/MessagingPublicSurfaceContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/messaging/MessagingPublicSurfaceContractTest.java
index c29034cf..8cd6a3cf 100644
--- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/messaging/MessagingPublicSurfaceContractTest.java
+++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/messaging/MessagingPublicSurfaceContractTest.java
@@ -270,6 +270,18 @@ class MessagingPublicSurfaceContractTest {
boolean versioned = lastColon > 0 && coordinate.indexOf(':') != lastColon;
exported.add(versioned ? coordinate.substring(0, lastColon) : coordinate);
}
+ // Version-catalog accessors are the same declaration in a different notation. Matching only the
+ // literal string form would have let `api libs.cloudevents.api` read as "not declared api" — a
+ // contract reported as broken because the build file was tidied, which is the most expensive
+ // kind of false positive a gate can produce.
+ Matcher catalogued =
+ Pattern.compile("(?m)^\\s*api\\s+(libs\\.[A-Za-z0-9.]+)").matcher(buildScript);
+ while (catalogued.find()) {
+ String module = catalogModules().get(catalogued.group(1));
+ if (module != null) {
+ exported.add(module);
+ }
+ }
Matcher projects =
Pattern.compile("(?m)^\\s*api\\s+project\\('([^']+)'\\)").matcher(buildScript);
while (projects.find()) {
@@ -294,9 +306,35 @@ class MessagingPublicSurfaceContractTest {
}
private static boolean declaresAsApi(String buildScript, String coordinate) {
- return Pattern.compile("(?m)^\\s*api\\s+'" + Pattern.quote(coordinate) + "(:[^']*)?'")
+ if (Pattern.compile("(?m)^\\s*api\\s+'" + Pattern.quote(coordinate) + "(:[^']*)?'")
.matcher(buildScript)
- .find();
+ .find()) {
+ return true;
+ }
+ return apiDependenciesOf(buildScript).contains(coordinate);
+ }
+
+ /**
+ * Version-catalog accessor to {@code group:artifact}, read from the catalog itself.
+ *
+ *