feat: add production capability foundations
This commit is contained in:
@@ -42,6 +42,15 @@ MongoDB, file server, object storage 같은 optional leaf는 독립적으로 빌
|
||||
잘못 설정된 채로 기동돼 트래픽을 받는 것보다, 기동 시점에 명확한 이유와 함께 멈추는 편이 안전하다.
|
||||
이 패키지는 그 "빨리·명확하게 실패시키기(fail-fast)"를 담당한다.
|
||||
|
||||
### AuthenticationModeCompositionConfig
|
||||
|
||||
`ca-skeleton.security.auth-mode`의 기본값은 `jwt`이며 `jwt|redis-session` 중 정확히 하나만 허용한다.
|
||||
JWT mode는 `jwtDecoder`가 있어야 하고 Redis session repository/filter가 있으면 기동을 거부한다.
|
||||
Redis session mode는 반대로 `jwtDecoder`를 거부하고 `redisVersionedSessionRepository`와
|
||||
`springSessionRepositoryFilter`가 모두 있어야 한다. 이 검증은 bean name만 확인하므로 bootstrap이
|
||||
Spring Session/Redis 구현 타입을 직접 의존하지 않으며, composition 누락과 이중 활성화를 context
|
||||
refresh 완료 전에 실패시킨다.
|
||||
|
||||
### FlywayProdSafetyValidator
|
||||
- **`prod` 프로파일에서 Flyway 안전장치가 꺼지지 못하도록 런타임에서 강제한다.** Flyway 옵션은
|
||||
`application.yml`에 안전값으로 고정돼 있지만(`baseline-on-migrate=false`, `out-of-order=false`,
|
||||
@@ -144,7 +153,7 @@ MongoDB, file server, object storage 같은 optional leaf는 독립적으로 빌
|
||||
### SecretSource
|
||||
- **시크릿 해석을 인터페이스 한 겹 뒤로 숨긴 backend seam 이다.** 시크릿이 필요한 코드는 이
|
||||
인터페이스에만 의존하고, 실제 백엔드(env / Vault / AWS Secrets Manager / GCP Secret Manager)는
|
||||
`SecretSourceFactory`가 설정값으로 고른다. `RateLimiter` / `RateLimiterFactory`와 같은 패턴이다.
|
||||
`SecretSourceFactory`가 설정값으로 고른다. 설정과 구현 선택을 한 factory에 모으는 패턴이다.
|
||||
백엔드를 추가하는 비용이 "새 `SecretSource` 구현 1개 + `SecretSourceStrategy` enum 값 1개 +
|
||||
factory case 1개"로 고정되고, 소비자(`SecretSourceValidator`, 향후 어댑터)는 전혀 손대지 않는다.
|
||||
- **빈 문자열은 "없음"으로 취급한다.** `resolve`가 blank 값을 `Optional.empty()`로 돌려주지 않으면,
|
||||
@@ -174,7 +183,7 @@ MongoDB, file server, object storage 같은 optional leaf는 독립적으로 빌
|
||||
### SecretSourceFactory
|
||||
- **유일한 확장 지점을 `switch` 하나로 모았다.** 새 백엔드는 `SecretSourceStrategy` 값 +
|
||||
`SecretSource` 구현 + 이 `switch`의 case 추가로 끝나고 소비자는 바뀌지 않는다.
|
||||
`RateLimiterFactory`와 같은 형태로, "확장 비용이 어디에 있는가"를 한곳에서 보이게 했다.
|
||||
factory 한곳에 "확장 비용이 어디에 있는가"를 보이게 했다.
|
||||
|
||||
### EnvironmentSecretSource
|
||||
- **기본 백엔드는 Spring `Environment`에서 읽는 것이다.** `ENVIRONMENT` 전략은 시크릿이 env var /
|
||||
|
||||
@@ -28,6 +28,12 @@ sourceSets {
|
||||
compileClasspath += sourceSets.main.output
|
||||
runtimeClasspath += sourceSets.main.output
|
||||
}
|
||||
redisCompositionTest {
|
||||
java.srcDir 'src/redisCompositionTest/java'
|
||||
resources.srcDir 'src/redisCompositionTest/resources'
|
||||
compileClasspath += sourceSets.main.output
|
||||
runtimeClasspath += sourceSets.main.output
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
@@ -37,6 +43,9 @@ configurations {
|
||||
sampleOffTestCompileOnly.extendsFrom testCompileOnly
|
||||
sampleOffTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||
sampleOffTestAnnotationProcessor.extendsFrom testAnnotationProcessor
|
||||
redisCompositionTestImplementation.extendsFrom testImplementation
|
||||
redisCompositionTestCompileOnly.extendsFrom testCompileOnly
|
||||
redisCompositionTestRuntimeOnly.extendsFrom testRuntimeOnly
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -47,6 +56,7 @@ dependencies {
|
||||
implementation project(':adapter:outbound:messaging')
|
||||
implementation project(':adapter:outbound:cache-redis')
|
||||
implementation project(':adapter:outbound:notification')
|
||||
implementation project(':adapter:outbound:fileserver')
|
||||
implementation project(':adapter:outbound:httpclient')
|
||||
implementation project(':adapter:outbound:identifier')
|
||||
implementation project(':adapter:inbound:web')
|
||||
@@ -140,6 +150,17 @@ tasks.register('sampleOffTest', Test) {
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
}
|
||||
|
||||
tasks.register('redisCompositionTest', Test) {
|
||||
group = 'redis verification'
|
||||
description = 'Runs Redis provider/role/security-mode composition and zero-side-effect contracts.'
|
||||
testClassesDirs = sourceSets.redisCompositionTest.output.classesDirs
|
||||
classpath = sourceSets.redisCompositionTest.runtimeClasspath
|
||||
useJUnitPlatform()
|
||||
failOnNoDiscoveredTests = true
|
||||
outputs.upToDateWhen { false }
|
||||
jvmArgs '-Duser.timezone=UTC'
|
||||
}
|
||||
|
||||
// The custom source set compiles the same test corpus, so it follows the repository-wide
|
||||
// warning-only policy already applied to checkstyleTest and spotbugsTest in the root build.
|
||||
tasks.named('checkstyleSampleOffTest') {
|
||||
|
||||
+276
-270
@@ -1,443 +1,449 @@
|
||||
# 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
|
||||
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
ch.qos.logback:logback-classic:1.5.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
aopalliance:aopalliance:1.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
ch.qos.logback:logback-classic:1.5.21=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
ch.qos.logback:logback-classic:1.5.34=sampleFixture
|
||||
ch.qos.logback:logback-core:1.5.21=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
ch.qos.logback:logback-core:1.5.21=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
ch.qos.logback:logback-core:1.5.34=sampleFixture
|
||||
com.approvaltests:approvaltests-util:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.approvaltests:approvaltests:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.approvaltests:approvaltests-util:31.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.approvaltests:approvaltests:31.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.20=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-annotations:2.21=sampleFixture
|
||||
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.20.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-core:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.20.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.core:jackson-databind:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.4=sampleFixture
|
||||
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.20.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml.jackson:jackson-bom:2.21.4=sampleFixture
|
||||
com.fasterxml:classmate:1.7.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml:classmate:1.7.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.fasterxml:classmate:1.7.3=sampleFixture
|
||||
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.github.docker-java:docker-java-api:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport-zerodep:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport:3.7.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.ben-manes.caffeine:caffeine:3.2.3=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.github.docker-java:docker-java-api:3.7.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport-zerodep:3.7.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.docker-java:docker-java-transport:3.7.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.github.f4b6a3:uuid-creator:6.1.1=sampleFixture,testRuntimeClasspath
|
||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs
|
||||
com.github.spotbugs:spotbugs-annotations:4.8.6=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
com.github.spotbugs:spotbugs-annotations:4.8.6=compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
com.github.spotbugs:spotbugs:4.10.2=spotbugs
|
||||
com.github.stephenc.jcip:jcip-annotations:1.0-1=sampleFixture,spotbugs
|
||||
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.auto:auto-common:1.2.2=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.code.findbugs:jsr305:3.0.2=checkstyle,compileClasspath,sampleOffTestCompileClasspath,spotbugs,testCompileClasspath
|
||||
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.auto:auto-common:1.2.2=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.code.findbugs:jsr305:3.0.2=checkstyle,compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,spotbugs,testCompileClasspath
|
||||
com.google.code.gson:gson:2.13.2=spotbugs
|
||||
com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
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,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:guava:33.5.0-jre=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:guava:33.5.0-jre=annotationProcessor,redisCompositionTestAnnotationProcessor,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,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins
|
||||
com.jayway.jsonpath:json-path:2.9.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.nimbusds:nimbus-jose-jwt:10.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.jayway.jsonpath:json-path:2.9.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.nimbusds:nimbus-jose-jwt:10.4=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.nimbusds:nimbus-jose-jwt:9.37.4=sampleFixture
|
||||
com.puppycrawl.tools:checkstyle:13.5.0=checkstyle
|
||||
com.squareup.okhttp3:okhttp-jvm:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.squareup.okhttp3:okhttp-jvm:5.2.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.squareup.okio:okio-jvm:3.16.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-api:1.3.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=redisCompositionTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5-engine:1.3.0=redisCompositionTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
com.tngtech.archunit:archunit-junit5:1.3.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.tngtech.archunit:archunit:1.3.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
com.vaadin.external.google:android-json:0.0.20131108.vaadin1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
commons-io:commons-io:2.21.0=spotbugs
|
||||
commons-logging:commons-logging:1.3.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
commons-logging:commons-logging:1.3.5=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
info.picocli:picocli:4.7.7=checkstyle
|
||||
io.github.cdimascio:dotenv-java:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,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.lettuce:lettuce-core:6.8.1.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.cdimascio:dotenv-java:3.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
io.github.resilience4j:resilience4j-bulkhead:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.resilience4j:resilience4j-core:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.resilience4j:resilience4j-micrometer:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.resilience4j:resilience4j-ratelimiter:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.resilience4j:resilience4j-retry:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.github.resilience4j:resilience4j-timelimiter:2.2.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.lettuce:lettuce-core:6.8.1.RELEASE=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.micrometer:context-propagation:1.1.4=sampleFixture
|
||||
io.micrometer:context-propagation:1.2.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:context-propagation:1.2.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-commons:1.15.12=sampleFixture
|
||||
io.micrometer:micrometer-commons:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-commons:1.16.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-core:1.15.12=sampleFixture
|
||||
io.micrometer:micrometer-core:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-core:1.16.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-jakarta9:1.15.12=sampleFixture
|
||||
io.micrometer:micrometer-jakarta9:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-jakarta9:1.16.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-observation:1.15.12=sampleFixture
|
||||
io.micrometer:micrometer-observation:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-observation:1.16.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-registry-prometheus:1.15.12=sampleFixture
|
||||
io.micrometer:micrometer-registry-prometheus:1.16.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-registry-prometheus:1.16.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-tracing-bridge-otel:1.5.12=sampleFixture
|
||||
io.micrometer:micrometer-tracing-bridge-otel:1.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-tracing-bridge-otel:1.6.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-tracing:1.5.12=sampleFixture
|
||||
io.micrometer:micrometer-tracing:1.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-buffer:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-base:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.micrometer:micrometer-tracing:1.6.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.netty:netty-buffer:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-base:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-compression:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-codec-dns:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-dns:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-codec-http2:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-codec-http:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-codec-marshalling:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-codec-protobuf:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-codec:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-common:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-handler:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-resolver-dns:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-resolver:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-common:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-handler:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-resolver-dns:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-resolver:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport-classes-epoll:4.2.7.Final=testRuntimeClasspath
|
||||
io.netty:netty-transport-native-unix-common:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport:4.2.7.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport-native-unix-common:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
io.netty:netty-transport:4.2.7.Final=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.projectreactor:reactor-core:3.7.19=sampleFixture
|
||||
io.projectreactor:reactor-core:3.8.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.projectreactor:reactor-core:3.8.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
io.smallrye:jandex:3.2.0=sampleFixture
|
||||
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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
jakarta.activation:jakarta.activation-api:2.1.4=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.annotation:jakarta.annotation-api:2.1.1=sampleFixture
|
||||
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.inject:jakarta.inject-api:2.0.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,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=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.transaction:jakarta.transaction-api:2.0.1=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.validation:jakarta.validation-api:3.0.2=sampleFixture
|
||||
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,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.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.websocket:jakarta.websocket-api:2.2.0=redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=sampleFixture
|
||||
javax.inject:javax.inject:1=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
javax.inject:javax.inject:1=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
jaxen:jaxen:2.0.0=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=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=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.minidev:json-smart:2.6.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy-agent:1.17.8=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.bytebuddy:byte-buddy:1.17.8=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.java.dev.jna:jna:5.18.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.minidev:accessors-smart:2.6.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.minidev:json-smart:2.6.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs
|
||||
org.antlr:antlr4-runtime:4.13.2=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.antlr:antlr4-runtime:4.13.2=checkstyle,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.commons:commons-lang3:3.20.0=checkstyle,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.httpcomponents:httpclient:4.5.13=checkstyle,testRuntimeClasspath
|
||||
org.apache.httpcomponents:httpcore:4.4.16=checkstyle,testRuntimeClasspath
|
||||
org.apache.kafka:kafka-clients:4.1.1=sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.apache.kafka:kafka-clients:4.1.1=redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.apache.logging.log4j:log4j-api:2.24.3=sampleFixture
|
||||
org.apache.logging.log4j:log4j-api:2.25.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.logging.log4j:log4j-api:2.25.2=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.logging.log4j:log4j-core:2.25.2=spotbugs
|
||||
org.apache.logging.log4j:log4j-to-slf4j:2.24.3=sampleFixture
|
||||
org.apache.logging.log4j:log4j-to-slf4j:2.25.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.logging.log4j:log4j-to-slf4j:2.25.2=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.14=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-core:11.0.14=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.14=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-el:11.0.14=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.14=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.tomcat.embed:tomcat-embed-websocket:11.0.14=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apache.xbean:xbean-reflect:3.7=checkstyle
|
||||
org.apiguardian:apiguardian-api:1.1.2=sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.aspectj:aspectjweaver:1.9.25=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.apiguardian:apiguardian-api:1.1.2=redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.aspectj:aspectjweaver:1.9.25=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.aspectj:aspectjweaver:1.9.25.1=sampleFixture
|
||||
org.assertj:assertj-core:3.27.6=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.awaitility:awaitility:4.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-qual:3.49.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.assertj:assertj-core:3.27.6=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.awaitility:awaitility:4.3.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.checkerframework:checker-qual:3.49.5=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
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.flywaydb:flyway-core:11.14.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.eclipse.angus:angus-activation:2.0.3=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-core:11.14.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-database-postgresql:11.7.2=sampleFixture
|
||||
org.glassfish.jaxb:jaxb-core:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-core:4.0.6=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-core:4.0.9=sampleFixture
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.6=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.9=sampleFixture
|
||||
org.glassfish.jaxb:txw2:4.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:txw2:4.0.6=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:txw2:4.0.9=sampleFixture
|
||||
org.hamcrest:hamcrest:3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.hamcrest:hamcrest:3.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.hibernate.orm:hibernate-core:6.6.53.Final=sampleFixture
|
||||
org.hibernate.orm:hibernate-core:7.1.8.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.hibernate.orm:hibernate-core:7.1.8.Final=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.hibernate.validator:hibernate-validator:8.0.3.Final=sampleFixture
|
||||
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.javassist:javassist:3.28.0-GA=checkstyle
|
||||
org.jboss.logging:jboss-logging:3.6.1.Final=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jboss.logging:jboss-logging:3.6.1.Final=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jboss.logging:jboss-logging:3.6.3.Final=sampleFixture
|
||||
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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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.0=annotationProcessor,checkstyle,compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:6.0.1=sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-testkit:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:6.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jetbrains:annotations:17.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,productionRuntimeClasspath,redisCompositionTestAnnotationProcessor,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:6.0.1=redisCompositionTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-testkit:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:6.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-junit-jupiter:5.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,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=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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-core:5.20.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-junit-jupiter:5.20.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.objenesis:objenesis:3.3=redisCompositionTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.openapitools:jackson-databind-nullable:0.2.6=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.3.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.osgi:org.osgi.resource:1.0.0=compileClasspath,redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.osgi:org.osgi.service.serviceloader:1.0.0=compileClasspath,redisCompositionTestCompileClasspath,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=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.pcollections:pcollections:4.0.1=annotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
org.ow2.asm:asm:9.7.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.pcollections:pcollections:4.0.1=annotationProcessor,redisCompositionTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor
|
||||
org.postgresql:postgresql:42.7.11=sampleFixture
|
||||
org.postgresql:postgresql:42.7.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.reactivestreams:reactive-streams:1.0.4=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.postgresql:postgresql:42.7.8=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.reactivestreams:reactive-streams:1.0.4=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.rnorth.duct-tape:duct-tape:1.0.8=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.skyscreamer:jsonassert:1.5.3=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:jul-to-slf4j:2.0.17=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:jul-to-slf4j:2.0.18=sampleFixture
|
||||
org.slf4j:slf4j-api:2.0.17=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:slf4j-api:2.0.17=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath
|
||||
org.slf4j:slf4j-api:2.0.18=sampleFixture
|
||||
org.slf4j:slf4j-simple:2.0.17=checkstyle,spotbugsSlf4j
|
||||
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=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-actuator:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-autoconfigure:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-autoconfigure:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-configuration-processor:4.0.0=annotationProcessor
|
||||
org.springframework.boot:spring-boot-data-commons:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-data-jpa-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-data-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-flyway:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-health:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-hibernate:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-http-client:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-http-converter:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jackson:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jdbc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jdbc:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jpa-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-micrometer-metrics:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-micrometer-observation:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-persistence:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-restclient:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-resttestclient:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-servlet:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-sql:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-data-commons:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-data-jpa-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-data-jpa:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-flyway:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-health:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-hibernate:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-http-client:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-http-converter:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jackson:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jdbc-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jdbc:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jpa-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-jpa:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-micrometer-metrics:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-micrometer-observation:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-persistence:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-restclient:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-resttestclient:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-security:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-servlet:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-sql:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-actuator:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-flyway:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-data-jpa:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-flyway:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jackson:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-jdbc:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-json:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-logging:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot-starter-logging:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-logging:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-security:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot-starter-security:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-security:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-tomcat:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-validation:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webmvc:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-web:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webmvc-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter-webmvc:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot-starter:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test-autoconfigure:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-tomcat:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-transaction:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-validation:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-web-server:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc-test:4.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-starter:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test-autoconfigure:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-tomcat:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-transaction:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-validation:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-web-server:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc-test:4.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot-webmvc:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.boot:spring-boot:3.5.16=sampleFixture
|
||||
org.springframework.boot:spring-boot:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.cloud:spring-cloud-context:4.1.4=sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.springframework.boot:spring-boot:4.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.cloud:spring-cloud-context:4.1.4=redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.springframework.data:spring-data-commons:3.5.13=sampleFixture
|
||||
org.springframework.data:spring-data-commons:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.data:spring-data-commons:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.data:spring-data-jpa:3.5.13=sampleFixture
|
||||
org.springframework.data:spring-data-jpa:4.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.data:spring-data-jpa:4.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.data:spring-data-keyvalue:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.data:spring-data-redis:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.integration:spring-integration-core:6.5.10=sampleFixture
|
||||
org.springframework.integration:spring-integration-core:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.integration:spring-integration-core:7.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.integration:spring-integration-jdbc:6.5.10=sampleFixture
|
||||
org.springframework.integration:spring-integration-jdbc:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.integration:spring-integration-jdbc:7.0.0=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,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.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-config:7.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-core:6.5.11=sampleFixture
|
||||
org.springframework.security:spring-security-core:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-core:7.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-crypto:6.5.11=sampleFixture
|
||||
org.springframework.security:spring-security-crypto:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-crypto:7.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-core:7.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-jose:6.5.11=sampleFixture
|
||||
org.springframework.security:spring-security-oauth2-jose:7.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-jose:7.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-test:7.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-oauth2-resource-server:7.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-test:7.0.0=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-web:6.5.11=sampleFixture
|
||||
org.springframework.security:spring-security-web:7.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.security:spring-security-web:7.0.0=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework.session:spring-session-core:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework.session:spring-session-data-redis:4.0.0=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-aop:6.2.19=sampleFixture
|
||||
org.springframework:spring-aop:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-aop:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-aspects:6.2.19=sampleFixture
|
||||
org.springframework:spring-aspects:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-aspects:7.0.1=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-beans:6.2.19=sampleFixture
|
||||
org.springframework:spring-beans:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-beans:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-context-support:7.0.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-context:6.2.19=sampleFixture
|
||||
org.springframework:spring-context:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-context:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-core:6.2.19=sampleFixture
|
||||
org.springframework:spring-core:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-core:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-expression:6.2.19=sampleFixture
|
||||
org.springframework:spring-expression:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-expression:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,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.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-jdbc:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-messaging:6.2.19=sampleFixture
|
||||
org.springframework:spring-messaging:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-messaging:7.0.1=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-orm:6.2.19=sampleFixture
|
||||
org.springframework:spring-orm:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-test:7.0.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-orm:7.0.1=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-oxm:7.0.1=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-test:7.0.1=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-tx:6.2.19=sampleFixture
|
||||
org.springframework:spring-tx:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-tx:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-web:6.2.19=sampleFixture
|
||||
org.springframework:spring-web:7.0.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-web:7.0.1=compileClasspath,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-webmvc:6.2.19=sampleFixture
|
||||
org.springframework:spring-webmvc:7.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-websocket:7.0.1=sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.testcontainers:testcontainers-database-commons:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers-jdbc:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers-junit-jupiter:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers-postgresql:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers:2.0.2=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-webmvc:7.0.1=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.springframework:spring-websocket:7.0.1=redisCompositionTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath
|
||||
org.testcontainers:testcontainers-database-commons:2.0.2=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers-jdbc:2.0.2=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers-junit-jupiter:2.0.2=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers-postgresql:2.0.2=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.testcontainers:testcontainers:2.0.2=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,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=redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.yaml:snakeyaml:2.4=sampleFixture
|
||||
org.yaml:snakeyaml:2.5=compileClasspath,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,productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
redis.clients.authentication:redis-authx-core:0.1.1-beta2=productionRuntimeClasspath,redisCompositionTestRuntimeClasspath,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
|
||||
@@ -468,7 +474,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.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-core:3.0.2=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson.core:jackson-databind:3.0.2=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
tools.jackson:jackson-bom:3.0.2=productionRuntimeClasspath,redisCompositionTestCompileClasspath,redisCompositionTestRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
empty=developmentOnly,testAndDevelopmentOnly
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package dev.caskeleton.bootstrap.httpclient;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientActivationResolver;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfigurationBinder;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientReadinessCardRegistry;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpOperationCatalogRegistry;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.ResolvedHttpClientCapability;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
|
||||
/**
|
||||
* HTTP capability composition root.
|
||||
*
|
||||
* <p>Only inert configuration, registries, and a sanitized descriptor are registered in the current
|
||||
* zero-binding implementation. No transport/provider configuration is imported.
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class HttpClientCompositionConfig {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HttpClientCanonicalConfiguration httpClientCanonicalConfiguration(Environment environment) {
|
||||
return new HttpClientCanonicalConfigurationBinder(Binder.get(environment)).bind();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HttpOperationCatalogRegistry httpOperationCatalogRegistry() {
|
||||
return HttpOperationCatalogRegistry.empty();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HttpClientReadinessCardRegistry httpClientReadinessCardRegistry() {
|
||||
return HttpClientReadinessCardRegistry.current();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
HttpClientActivationResolver httpClientActivationResolver() {
|
||||
return new HttpClientActivationResolver();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResolvedHttpClientCapability resolvedHttpClientCapability(
|
||||
HttpClientCanonicalConfiguration configuration,
|
||||
HttpOperationCatalogRegistry catalogs,
|
||||
HttpClientReadinessCardRegistry readinessCards,
|
||||
HttpClientActivationResolver resolver) {
|
||||
return resolver.resolve(configuration, catalogs, readinessCards);
|
||||
}
|
||||
}
|
||||
+5
@@ -3,6 +3,7 @@ package dev.caskeleton.bootstrap.idempotency;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyExecutor;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStorePort;
|
||||
import java.time.Clock;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
@@ -21,6 +22,10 @@ public class IdempotencyConfig {
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
name = "ca-skeleton.capabilities.idempotency.provider",
|
||||
havingValue = "jdbc",
|
||||
matchIfMissing = true)
|
||||
public IdempotencyExecutor idempotencyExecutor(
|
||||
IdempotencyStorePort store, Clock clock, IdempotencySettings properties) {
|
||||
return new IdempotencyExecutor(store, clock, properties.ttl());
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package dev.caskeleton.bootstrap.idempotency;
|
||||
|
||||
import dev.caskeleton.application.idempotency.IdempotencyExecutor;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyExecutorV2;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStorePort;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStorePortV2;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** Fail-fast exclusivity guard for the JDBC V1 and Redis V2 request-replay providers. */
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(IdempotencyProviderSettings.class)
|
||||
public class IdempotencyProviderSelectionConfig {
|
||||
|
||||
@Bean
|
||||
SmartInitializingSingleton idempotencyProviderExclusivity(
|
||||
IdempotencyProviderSettings settings,
|
||||
ObjectProvider<IdempotencyStorePort> jdbcStores,
|
||||
ObjectProvider<IdempotencyExecutor> jdbcExecutors,
|
||||
ObjectProvider<IdempotencyStorePortV2> redisStores,
|
||||
ObjectProvider<IdempotencyExecutorV2> redisExecutors) {
|
||||
return () -> {
|
||||
int jdbcStoreCount = count(jdbcStores);
|
||||
int jdbcExecutorCount = count(jdbcExecutors);
|
||||
int redisStoreCount = count(redisStores);
|
||||
int redisExecutorCount = count(redisExecutors);
|
||||
switch (settings.provider()) {
|
||||
case DISABLED ->
|
||||
requireCounts(
|
||||
jdbcStoreCount, jdbcExecutorCount, redisStoreCount, redisExecutorCount, 0, 0, 0, 0);
|
||||
case JDBC ->
|
||||
requireCounts(
|
||||
jdbcStoreCount, jdbcExecutorCount, redisStoreCount, redisExecutorCount, 1, 1, 0, 0);
|
||||
case REDIS ->
|
||||
requireCounts(
|
||||
jdbcStoreCount, jdbcExecutorCount, redisStoreCount, redisExecutorCount, 0, 0, 1, 1);
|
||||
default ->
|
||||
throw new IllegalStateException(
|
||||
"Unsupported idempotency provider: " + settings.provider());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static int count(ObjectProvider<?> beans) {
|
||||
return Math.toIntExact(beans.stream().count());
|
||||
}
|
||||
|
||||
private static void requireCounts(
|
||||
int jdbcStores,
|
||||
int jdbcExecutors,
|
||||
int redisStores,
|
||||
int redisExecutors,
|
||||
int expectedJdbcStores,
|
||||
int expectedJdbcExecutors,
|
||||
int expectedRedisStores,
|
||||
int expectedRedisExecutors) {
|
||||
if (jdbcStores != expectedJdbcStores
|
||||
|| jdbcExecutors != expectedJdbcExecutors
|
||||
|| redisStores != expectedRedisStores
|
||||
|| redisExecutors != expectedRedisExecutors) {
|
||||
throw new IllegalStateException(
|
||||
"Idempotency provider selection is ambiguous or incomplete: exactly the selected "
|
||||
+ "JDBC V1 or Redis V2 store/executor pair must be active");
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
}
|
||||
|
||||
public enum Provider {
|
||||
DISABLED,
|
||||
JDBC,
|
||||
REDIS
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.DestroyableRedisSecret;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial;
|
||||
import dev.caskeleton.bootstrap.runtime.SecretSource;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Strict {@code secret://environment/ENV_KEY} bridge for canonical Redis material.
|
||||
*
|
||||
* <p>The environment backend has no change-event stream. Rotation therefore requires process
|
||||
* restart or an explicit runtime recomposition initiated by an operator.
|
||||
*/
|
||||
public final class RedisEnvironmentCredentialMaterialProvider
|
||||
implements RedisCredentialMaterialProvider {
|
||||
|
||||
private static final int MAXIMUM_CREDENTIAL_CHARS = 16_384;
|
||||
private static final String VERSION = "environment-restart-v1";
|
||||
private final RedisEnvironmentMaterialResolver resolver;
|
||||
|
||||
public RedisEnvironmentCredentialMaterialProvider(SecretSource secretSource) {
|
||||
this.resolver = new RedisEnvironmentMaterialResolver(secretSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionedRedisCredentialMaterial resolve(RedisSecretReference reference) {
|
||||
String value = resolver.resolve(reference, MAXIMUM_CREDENTIAL_CHARS);
|
||||
char[] mutable = value.toCharArray();
|
||||
try {
|
||||
return new VersionedRedisCredentialMaterial(
|
||||
VERSION, Instant.MAX, DestroyableRedisSecret.from(mutable));
|
||||
} finally {
|
||||
java.util.Arrays.fill(mutable, '\0');
|
||||
}
|
||||
}
|
||||
|
||||
public RedisEnvironmentMaterialProviderDescriptor descriptor() {
|
||||
return RedisEnvironmentMaterialProviderDescriptor.restartOrExplicitRecomposition();
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import dev.caskeleton.bootstrap.runtime.SecretSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** Registers the environment-backed material bridge without resolving any secret eagerly. */
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RedisEnvironmentMaterialConfig {
|
||||
|
||||
@Bean
|
||||
RedisEnvironmentCredentialMaterialProvider redisEnvironmentCredentialMaterialProvider(
|
||||
SecretSource secretSource) {
|
||||
return new RedisEnvironmentCredentialMaterialProvider(secretSource);
|
||||
}
|
||||
|
||||
@Bean
|
||||
RedisEnvironmentTrustMaterialProvider redisEnvironmentTrustMaterialProvider(
|
||||
SecretSource secretSource) {
|
||||
return new RedisEnvironmentTrustMaterialProvider(secretSource);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
/** Honest operational capability descriptor for the environment-backed Redis material bridge. */
|
||||
public record RedisEnvironmentMaterialProviderDescriptor(
|
||||
String provider, boolean changeEventsSupported, String refreshMode) {
|
||||
|
||||
static RedisEnvironmentMaterialProviderDescriptor restartOrExplicitRecomposition() {
|
||||
return new RedisEnvironmentMaterialProviderDescriptor(
|
||||
"environment", false, "restart-or-explicit-runtime-recomposition");
|
||||
}
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference;
|
||||
import dev.caskeleton.bootstrap.runtime.SecretSource;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/** Shared strict parser/resolver for the two environment material provider interfaces. */
|
||||
final class RedisEnvironmentMaterialResolver {
|
||||
|
||||
private static final String PREFIX = "secret://environment/";
|
||||
private static final Set<String> ALLOWED_KEYS =
|
||||
Set.of(
|
||||
"APP_CACHE_REDIS_PASSWORD",
|
||||
"APP_CACHE_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_CACHE_REDIS_TRUST_PEM",
|
||||
"APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_LEASE_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_RATE_LIMIT_REDIS_PASSWORD",
|
||||
"APP_RATE_LIMIT_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_RATE_LIMIT_REDIS_TRUST_PEM",
|
||||
"APP_SESSION_REDIS_PASSWORD",
|
||||
"APP_SESSION_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_SESSION_REDIS_TRUST_PEM");
|
||||
|
||||
private final SecretSource secretSource;
|
||||
|
||||
RedisEnvironmentMaterialResolver(SecretSource secretSource) {
|
||||
this.secretSource = Objects.requireNonNull(secretSource, "secretSource must be non-null");
|
||||
}
|
||||
|
||||
String resolve(RedisSecretReference reference, int maximumLength) {
|
||||
Objects.requireNonNull(reference, "reference must be non-null");
|
||||
String key = parseAllowedKey(reference.valueForResolution());
|
||||
try {
|
||||
String value =
|
||||
secretSource.resolve(key).orElseThrow(RedisEnvironmentMaterialResolver::materialFailure);
|
||||
if (value.isBlank() || value.length() > maximumLength) {
|
||||
throw materialFailure();
|
||||
}
|
||||
return value;
|
||||
} catch (RedisEnvironmentMaterialException exception) {
|
||||
throw exception;
|
||||
} catch (RuntimeException ignored) {
|
||||
throw materialFailure();
|
||||
}
|
||||
}
|
||||
|
||||
private static String parseAllowedKey(String reference) {
|
||||
if (reference == null
|
||||
|| !reference.startsWith(PREFIX)
|
||||
|| reference.length() <= PREFIX.length()) {
|
||||
throw materialFailure();
|
||||
}
|
||||
String key = reference.substring(PREFIX.length());
|
||||
if (!key.matches("[A-Z][A-Z0-9_]{0,127}") || !ALLOWED_KEYS.contains(key)) {
|
||||
throw materialFailure();
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
static RedisEnvironmentMaterialException materialFailure() {
|
||||
return new RedisEnvironmentMaterialException(
|
||||
"Canonical Redis environment material resolution failed");
|
||||
}
|
||||
|
||||
static final class RedisEnvironmentMaterialException extends IllegalStateException {
|
||||
|
||||
private RedisEnvironmentMaterialException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.DestroyableRedisPem;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisTrustMaterialProvider;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisTrustMaterial;
|
||||
import dev.caskeleton.bootstrap.runtime.SecretSource;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** Environment-backed, restart-only Redis trust material provider. */
|
||||
public final class RedisEnvironmentTrustMaterialProvider implements RedisTrustMaterialProvider {
|
||||
|
||||
private static final int MAXIMUM_TRUST_BYTES = 1_048_576;
|
||||
private static final String VERSION = "environment-restart-v1";
|
||||
private final RedisEnvironmentMaterialResolver resolver;
|
||||
|
||||
public RedisEnvironmentTrustMaterialProvider(SecretSource secretSource) {
|
||||
this.resolver = new RedisEnvironmentMaterialResolver(secretSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionedRedisTrustMaterial resolve(RedisSecretReference reference) {
|
||||
String value = resolver.resolve(reference, MAXIMUM_TRUST_BYTES);
|
||||
byte[] encoded = value.getBytes(StandardCharsets.UTF_8);
|
||||
if (encoded.length > MAXIMUM_TRUST_BYTES) {
|
||||
Arrays.fill(encoded, (byte) 0);
|
||||
throw RedisEnvironmentMaterialResolver.materialFailure();
|
||||
}
|
||||
try {
|
||||
return new VersionedRedisTrustMaterial(
|
||||
VERSION, Instant.MAX, DestroyableRedisPem.from(encoded));
|
||||
} finally {
|
||||
Arrays.fill(encoded, (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
public RedisEnvironmentMaterialProviderDescriptor descriptor() {
|
||||
return RedisEnvironmentMaterialProviderDescriptor.restartOrExplicitRecomposition();
|
||||
}
|
||||
}
|
||||
+57
@@ -32,6 +32,12 @@ public class SecretSourceValidator implements SmartInitializingSingleton {
|
||||
"APP_EXTERNAL_API_KEY",
|
||||
"APP_CACHE_REDIS_PASSWORD",
|
||||
"APP_CACHE_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_RATE_LIMIT_REDIS_PASSWORD",
|
||||
"APP_RATE_LIMIT_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_SESSION_REDIS_PASSWORD",
|
||||
"APP_SESSION_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_LEASE_REDIS_KEY_HMAC_SECRET",
|
||||
"APP_PRIVACY_PSEUDONYMIZATION_SALT");
|
||||
|
||||
private final ConfigurableEnvironment environment;
|
||||
@@ -82,6 +88,18 @@ public class SecretSourceValidator implements SmartInitializingSingleton {
|
||||
}
|
||||
List<String> missing = new ArrayList<>();
|
||||
for (String key : REQUIRED_PROD_SECRETS) {
|
||||
if (isRateLimitSecret(key) && !isRedisRateLimitProviderSelected()) {
|
||||
continue;
|
||||
}
|
||||
if (isSessionRedisMaterial(key) && !isRedisSessionSelected()) {
|
||||
continue;
|
||||
}
|
||||
if (isIdempotencyRedisMaterial(key) && !isRedisIdempotencyProviderSelected()) {
|
||||
continue;
|
||||
}
|
||||
if (isLeaseRedisMaterial(key) && !isRedisLeaseProviderSelected()) {
|
||||
continue;
|
||||
}
|
||||
if (secretSource.resolve(key).isEmpty()) {
|
||||
missing.add(key);
|
||||
}
|
||||
@@ -95,6 +113,45 @@ public class SecretSourceValidator implements SmartInitializingSingleton {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isRateLimitSecret(String key) {
|
||||
return key.startsWith("APP_RATE_LIMIT_REDIS_");
|
||||
}
|
||||
|
||||
private boolean isRedisRateLimitProviderSelected() {
|
||||
return "redis"
|
||||
.equalsIgnoreCase(
|
||||
environment.getProperty("ca-skeleton.capabilities.rate-limit.provider", "disabled"));
|
||||
}
|
||||
|
||||
private static boolean isSessionRedisMaterial(String key) {
|
||||
return key.startsWith("APP_SESSION_REDIS_");
|
||||
}
|
||||
|
||||
private boolean isRedisSessionSelected() {
|
||||
return "redis-session"
|
||||
.equalsIgnoreCase(environment.getProperty("ca-skeleton.security.auth-mode", "jwt"));
|
||||
}
|
||||
|
||||
private static boolean isIdempotencyRedisMaterial(String key) {
|
||||
return key.startsWith("APP_IDEMPOTENCY_REDIS_");
|
||||
}
|
||||
|
||||
private boolean isRedisIdempotencyProviderSelected() {
|
||||
return "redis"
|
||||
.equalsIgnoreCase(
|
||||
environment.getProperty("ca-skeleton.capabilities.idempotency.provider", "jdbc"));
|
||||
}
|
||||
|
||||
private static boolean isLeaseRedisMaterial(String key) {
|
||||
return key.startsWith("APP_LEASE_REDIS_");
|
||||
}
|
||||
|
||||
private boolean isRedisLeaseProviderSelected() {
|
||||
return "redis"
|
||||
.equalsIgnoreCase(
|
||||
environment.getProperty("ca-skeleton.capabilities.lease.provider", "disabled"));
|
||||
}
|
||||
|
||||
private boolean isProdActive() {
|
||||
// Case-insensitive: a typo such as SPRING_PROFILES_ACTIVE=PROD must still match.
|
||||
for (String profile : environment.getActiveProfiles()) {
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package dev.caskeleton.bootstrap.runtime.redis;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.config.RedisRole;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Role;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.RoleHealth;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.State;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.springframework.boot.health.contributor.Health;
|
||||
import org.springframework.boot.health.contributor.HealthIndicator;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Condition;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.context.annotation.Conditional;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
/** Maps the framework-neutral Redis role snapshot into the bootstrap-owned health framework. */
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RedisHealthContributorConfig {
|
||||
|
||||
@Bean("redisRequired")
|
||||
@Conditional(RequiredRedisRoleCondition.class)
|
||||
HealthIndicator redisRequired(RedisHealthSnapshotProvider snapshots, Environment environment) {
|
||||
Set<Role> expected = expectedRequiredRoles(environment);
|
||||
return () -> requiredHealth(snapshots, expected);
|
||||
}
|
||||
|
||||
@Bean("redisOptional")
|
||||
@Conditional(CacheRedisRoleCondition.class)
|
||||
HealthIndicator redisOptional(RedisHealthSnapshotProvider snapshots) {
|
||||
return () -> optionalCacheHealth(snapshots);
|
||||
}
|
||||
|
||||
private static Health requiredHealth(RedisHealthSnapshotProvider snapshots, Set<Role> expected) {
|
||||
try {
|
||||
List<RoleHealth> roles =
|
||||
snapshots.snapshot().roles().stream()
|
||||
.filter(role -> expected.contains(role.role()) && role.required())
|
||||
.sorted(Comparator.comparing(RoleHealth::role))
|
||||
.toList();
|
||||
boolean complete =
|
||||
roles.stream()
|
||||
.map(RoleHealth::role)
|
||||
.collect(java.util.stream.Collectors.toSet())
|
||||
.containsAll(expected);
|
||||
boolean available =
|
||||
complete && roles.stream().allMatch(role -> role.state() == State.AVAILABLE);
|
||||
Health.Builder health = available ? Health.up() : Health.down();
|
||||
return health
|
||||
.withDetail("state", available ? "AVAILABLE" : "UNAVAILABLE")
|
||||
.withDetail("roles", details(roles))
|
||||
.withDetail("missingRoles", missing(expected, roles))
|
||||
.build();
|
||||
} catch (RuntimeException exception) {
|
||||
return Health.down()
|
||||
.withDetail("state", "UNAVAILABLE")
|
||||
.withDetail("reason", "SNAPSHOT_UNAVAILABLE")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static Health optionalCacheHealth(RedisHealthSnapshotProvider snapshots) {
|
||||
try {
|
||||
List<RoleHealth> roles =
|
||||
snapshots.snapshot().roles().stream()
|
||||
.filter(role -> role.role() == Role.CACHE && !role.required())
|
||||
.toList();
|
||||
boolean available = roles.size() == 1 && roles.getFirst().state() == State.AVAILABLE;
|
||||
return Health.up()
|
||||
.withDetail("state", available ? "AVAILABLE" : "DEGRADED")
|
||||
.withDetail("roles", details(roles))
|
||||
.withDetail("missingRoles", roles.isEmpty() ? List.of(Role.CACHE.name()) : List.of())
|
||||
.build();
|
||||
} catch (RuntimeException exception) {
|
||||
return Health.up()
|
||||
.withDetail("state", "DEGRADED")
|
||||
.withDetail("reason", "SNAPSHOT_UNAVAILABLE")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Map<String, Object>> details(List<RoleHealth> roles) {
|
||||
List<Map<String, Object>> result = new ArrayList<>(roles.size());
|
||||
for (RoleHealth role : roles) {
|
||||
Map<String, Object> detail = new LinkedHashMap<>();
|
||||
detail.put("role", role.role().name());
|
||||
detail.put("capabilities", role.capabilities().stream().map(Enum::name).sorted().toList());
|
||||
detail.put("state", role.state().name());
|
||||
detail.put("reason", role.reason().name());
|
||||
detail.put("semanticObservedAt", role.semanticObservedAt().toString());
|
||||
detail.put("semanticAgeMillis", role.semanticAgeMillis());
|
||||
detail.put("semanticStale", role.semanticStale());
|
||||
detail.put("expectedEviction", role.expectedEviction().name());
|
||||
detail.put("evictionAttestation", role.evictionAttestation().name());
|
||||
detail.put("externalEvictionAttestation", "INCOMPLETE");
|
||||
result.add(Map.copyOf(detail));
|
||||
}
|
||||
return List.copyOf(result);
|
||||
}
|
||||
|
||||
private static List<String> missing(Set<Role> expected, List<RoleHealth> actual) {
|
||||
EnumSet<Role> missing = EnumSet.noneOf(Role.class);
|
||||
missing.addAll(expected);
|
||||
actual.forEach(role -> missing.remove(role.role()));
|
||||
return missing.stream().map(Enum::name).toList();
|
||||
}
|
||||
|
||||
private static Set<Role> expectedRequiredRoles(Environment environment) {
|
||||
EnumSet<Role> roles = EnumSet.noneOf(Role.class);
|
||||
if (selected(environment, RedisRole.COORDINATION)) {
|
||||
roles.add(Role.COORDINATION);
|
||||
}
|
||||
if (selected(environment, RedisRole.SESSION)) {
|
||||
roles.add(Role.SESSION);
|
||||
}
|
||||
return Set.copyOf(roles);
|
||||
}
|
||||
|
||||
private static boolean selected(Environment environment, RedisRole role) {
|
||||
return !RedisCanonicalConfig.selectedCapabilities(environment)
|
||||
.getOrDefault(role, Set.of())
|
||||
.isEmpty();
|
||||
}
|
||||
|
||||
static final class RequiredRedisRoleCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
Environment environment = context.getEnvironment();
|
||||
return selected(environment, RedisRole.COORDINATION)
|
||||
|| selected(environment, RedisRole.SESSION);
|
||||
}
|
||||
}
|
||||
|
||||
static final class CacheRedisRoleCondition implements Condition {
|
||||
|
||||
@Override
|
||||
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
|
||||
return selected(context.getEnvironment(), RedisRole.CACHE);
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package dev.caskeleton.bootstrap.security;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** Fails startup when JWT and Redis Session infrastructure are both present or both absent. */
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@EnableConfigurationProperties(AuthenticationModeSettings.class)
|
||||
public class AuthenticationModeCompositionConfig {
|
||||
|
||||
@Bean
|
||||
SmartInitializingSingleton authenticationModeCompositionValidator(
|
||||
AuthenticationModeSettings settings, ListableBeanFactory beans) {
|
||||
return () -> validate(settings.authMode(), beans);
|
||||
}
|
||||
|
||||
static void validate(
|
||||
AuthenticationModeSettings.AuthenticationMode mode, ListableBeanFactory beans) {
|
||||
List<String> 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");
|
||||
}
|
||||
}
|
||||
if (!contradictions.isEmpty()) {
|
||||
throw new IllegalStateException(
|
||||
"Authentication mode composition is not exclusive for " + mode + ": " + contradictions);
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package dev.caskeleton.bootstrap.security;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.bind.ConstructorBinding;
|
||||
|
||||
/** Composition-root authority for the exclusive application authentication mode. */
|
||||
@ConfigurationProperties(prefix = "ca-skeleton.security")
|
||||
public record AuthenticationModeSettings(AuthenticationMode authMode) {
|
||||
|
||||
@ConstructorBinding
|
||||
public AuthenticationModeSettings(AuthenticationMode authMode) {
|
||||
this.authMode = authMode == null ? AuthenticationMode.JWT : authMode;
|
||||
}
|
||||
|
||||
public enum AuthenticationMode {
|
||||
JWT,
|
||||
REDIS_SESSION
|
||||
}
|
||||
}
|
||||
@@ -233,6 +233,9 @@ management:
|
||||
health:
|
||||
# D8: never expose health details to unauthenticated callers.
|
||||
show-details: when-authorized
|
||||
# redisRequired exists only when a correctness role is bound. Keep the static group
|
||||
# fail-closed for known names while allowing an absent conditional contributor.
|
||||
validate-group-membership: false
|
||||
# feature-runtime-health-lifecycle-contract: expose the Kubernetes-ready
|
||||
# liveness/readiness/startup probe paths.
|
||||
probes:
|
||||
@@ -244,11 +247,11 @@ management:
|
||||
liveness:
|
||||
include: livenessState
|
||||
# Readiness: ready to serve traffic AND all REQUIRED dependencies up.
|
||||
# Required: db (primary DB — auto-contributed by Spring Boot DataSource).
|
||||
# Optional: cache / broker / notification adapters are NOT in this group
|
||||
# (they are conditional or optional per the dependency taxonomy).
|
||||
# Required: db and redisRequired (only COORDINATION/SESSION role bindings).
|
||||
# redisOptional is deliberately excluded: a CACHE outage is reported as degraded detail
|
||||
# but never turns a healthy JVM or an otherwise-ready pod unavailable.
|
||||
readiness:
|
||||
include: readinessState,db
|
||||
include: readinessState,db,redisRequired
|
||||
# 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).
|
||||
@@ -299,6 +302,126 @@ logging:
|
||||
# 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:
|
||||
# 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:
|
||||
cache:
|
||||
# Canonical semantic cache activation. Disabled by default; "redis" requires an active
|
||||
# ca-skeleton.providers.redis.roles.cache binding and resolves HMAC material by reference.
|
||||
bindings:
|
||||
default: ${APP_CACHE_CANONICAL_DEFAULT_PROVIDER:disabled}
|
||||
regions:
|
||||
default:
|
||||
key-hmac-secret-reference: secret://environment/APP_CACHE_REDIS_KEY_HMAC_SECRET
|
||||
namespace-application: ${APP_NAME:ca-skeleton}
|
||||
namespace-environment: ${APP_CACHE_REDIS_NAMESPACE_ENVIRONMENT:local}
|
||||
semantic-region: ${APP_CACHE_REDIS_SEMANTIC_REGION:default}
|
||||
hash-key-version: 1
|
||||
key-version: 1
|
||||
policy-revision: canonical-default-r1
|
||||
positive-soft-ttl: ${APP_CACHE_REDIS_POSITIVE_SOFT_TTL:240s}
|
||||
positive-hard-ttl: ${APP_CACHE_DEFAULT_TTL:300s}
|
||||
negative-ttl: ${APP_CACHE_NEGATIVE_TTL:60s}
|
||||
ttl-jitter: ${APP_CACHE_REDIS_TTL_JITTER:0.10}
|
||||
maximum-value-bytes: 61440
|
||||
l1:
|
||||
enabled: ${APP_CACHE_REDIS_L1_ENABLED:false}
|
||||
maximum-entries: ${APP_CACHE_REDIS_L1_MAXIMUM_ENTRIES:10000}
|
||||
maximum-weight-bytes: ${APP_CACHE_REDIS_L1_MAXIMUM_WEIGHT_BYTES:67108864}
|
||||
maximum-entry-weight-bytes: ${APP_CACHE_REDIS_L1_MAXIMUM_ENTRY_WEIGHT_BYTES:1048576}
|
||||
time-to-live: ${APP_CACHE_REDIS_L1_TTL:30s}
|
||||
generation-recheck-interval: ${APP_CACHE_REDIS_L1_GENERATION_RECHECK_INTERVAL:5s}
|
||||
invalidation-queue-capacity: ${APP_CACHE_REDIS_L1_INVALIDATION_QUEUE_CAPACITY:1024}
|
||||
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:jdbc}
|
||||
key-hmac-secret-reference: secret://environment/APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET
|
||||
namespace-application: ${APP_NAME:ca-skeleton}
|
||||
namespace-environment: ${APP_IDEMPOTENCY_REDIS_NAMESPACE_ENVIRONMENT:local}
|
||||
hash-key-version: 1
|
||||
key-version: 1
|
||||
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
|
||||
policy-revision: request-replay-v2
|
||||
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
|
||||
namespace-application: ${APP_NAME:ca-skeleton}
|
||||
namespace-environment: ${APP_LEASE_REDIS_NAMESPACE_ENVIRONMENT:local}
|
||||
hash-key-version: 1
|
||||
key-version: 1
|
||||
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}
|
||||
hash-key-version: ${APP_RATE_LIMIT_HASH_KEY_VERSION:1}
|
||||
key-version: ${APP_RATE_LIMIT_KEY_VERSION:1}
|
||||
key-hmac-secret-reference: secret://environment/APP_RATE_LIMIT_REDIS_KEY_HMAC_SECRET
|
||||
namespace-application: ${APP_NAME:ca-skeleton}
|
||||
namespace-environment: ${APP_RATE_LIMIT_REDIS_NAMESPACE_ENVIRONMENT:local}
|
||||
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:
|
||||
redis-session:
|
||||
key-hmac-secret-reference: secret://environment/APP_SESSION_REDIS_KEY_HMAC_SECRET
|
||||
namespace-application: ${APP_NAME:ca-skeleton}
|
||||
namespace-environment: ${APP_SESSION_REDIS_NAMESPACE_ENVIRONMENT:local}
|
||||
hash-key-version: 1
|
||||
key-version: 1
|
||||
idle-timeout: ${APP_SESSION_IDLE_TIMEOUT:30m}
|
||||
absolute-lifetime: ${APP_SESSION_ABSOLUTE_LIFETIME:8h}
|
||||
touch-interval: ${APP_SESSION_TOUCH_INTERVAL:1m}
|
||||
tombstone-time-to-live: ${APP_SESSION_TOMBSTONE_TTL:5m}
|
||||
maximum-envelope-bytes: ${APP_SESSION_MAXIMUM_ENVELOPE_BYTES:32768}
|
||||
maximum-attributes: ${APP_SESSION_MAXIMUM_ATTRIBUTES:64}
|
||||
maximum-scalar-bytes: ${APP_SESSION_MAXIMUM_SCALAR_BYTES:8192}
|
||||
http-client:
|
||||
expected-state: DISABLED
|
||||
bindings: {}
|
||||
providers:
|
||||
http-client: {}
|
||||
redis:
|
||||
# Definitions alone are inert. Environment-specific configuration must bind roles.
|
||||
legacy-migration-enabled: false
|
||||
deployments: {}
|
||||
roles: {}
|
||||
runtime:
|
||||
client-name: canonical-redis
|
||||
connect-timeout: 2s
|
||||
tls-handshake-timeout: 3s
|
||||
acquire-timeout: 2s
|
||||
command-timeout: 2s
|
||||
overall-timeout: 5s
|
||||
shutdown-timeout: 3s
|
||||
maximum-queued-commands: 64
|
||||
cluster-maximum-redirects: 5
|
||||
cluster-topology-refresh-period: 30s
|
||||
maximum-in-flight-commands: 64
|
||||
maximum-command-bytes: 65536
|
||||
maximum-in-flight-bytes: 4194304
|
||||
route-drain-timeout: 6s
|
||||
default-write-ttl: 5m
|
||||
sentinel-discovery-refresh-period: ${APP_REDIS_SENTINEL_DISCOVERY_REFRESH_PERIOD:30s}
|
||||
semantic-probe-minimum-interval: ${APP_REDIS_SEMANTIC_PROBE_MINIMUM_INTERVAL:5s}
|
||||
semantic-probe-maximum-staleness: ${APP_REDIS_SEMANTIC_PROBE_MAXIMUM_STALENESS:15s}
|
||||
bootstrap:
|
||||
# required, non-blank — startup fails if blank (see BootstrapSettings)
|
||||
app-name: ${APP_NAME}
|
||||
@@ -326,18 +449,6 @@ ca-skeleton:
|
||||
# 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}
|
||||
rate-limit:
|
||||
# feature-rate-limit-idempotency-contract D1/§G. enabled is env-driven
|
||||
# (restart-only); limit/window are the fixed-window mechanism's literal tuning
|
||||
# parameters (no env key — UNSUPPORTED_IMPL per-key counter, single-node D5).
|
||||
enabled: ${APP_RATE_LIMIT_ENABLED}
|
||||
limit: 100
|
||||
window: 1s
|
||||
# RateLimiter strategy: fixed-window (default) | (extend: sliding-window | token-bucket)
|
||||
algorithm: fixed-window
|
||||
# Client IP source for unauthenticated rate-limit keys:
|
||||
# remote-addr-only (safe default) | forwarded-headers-trusted (only behind trusted ingress/LB)
|
||||
client-ip-mode: ${APP_RATE_LIMIT_CLIENT_IP_MODE:remote-addr-only}
|
||||
idempotency:
|
||||
# feature-rate-limit-idempotency-contract D6/§E. ttl is env-driven (<=72h,
|
||||
# validated in IdempotencyProperties); reaper-interval is literal operational tuning.
|
||||
@@ -372,12 +483,22 @@ ca-skeleton:
|
||||
# read also by adapter-persistence OutboxReaper via ${ca-skeleton.outbox.published-retention:P7D}
|
||||
published-retention: P7D
|
||||
security:
|
||||
# REQUIRED; startup fails if blank
|
||||
# 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<String>)
|
||||
public-paths: ${SECURITY_PUBLIC_PATHS}
|
||||
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
|
||||
@@ -466,6 +587,42 @@ ca-skeleton:
|
||||
# (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:
|
||||
# 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"
|
||||
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}
|
||||
cache:
|
||||
redis:
|
||||
# true | false (boolean_strict). Redis cache adapter on/off.
|
||||
@@ -481,11 +638,24 @@ app:
|
||||
maximum-queued-commands: ${APP_CACHE_REDIS_MAXIMUM_QUEUED_COMMANDS:8}
|
||||
maximum-in-flight-bytes: ${APP_CACHE_REDIS_MAXIMUM_IN_FLIGHT_BYTES:16777216}
|
||||
positive-ttl: ${APP_CACHE_DEFAULT_TTL:300s}
|
||||
# Blank derives 80% of positive-ttl in typed settings.
|
||||
positive-soft-ttl: ${APP_CACHE_REDIS_POSITIVE_SOFT_TTL:}
|
||||
negative-ttl: ${APP_CACHE_NEGATIVE_TTL:60s}
|
||||
ttl-jitter: ${APP_CACHE_REDIS_TTL_JITTER:0.10}
|
||||
minimum-hard-ttl: ${APP_CACHE_REDIS_MINIMUM_HARD_TTL:1s}
|
||||
namespace-application: ${APP_NAME:ca-skeleton}
|
||||
namespace-environment: ${APP_CACHE_REDIS_NAMESPACE_ENVIRONMENT:local}
|
||||
semantic-region: ${APP_CACHE_REDIS_SEMANTIC_REGION:default}
|
||||
maximum-value-bytes: ${APP_CACHE_REDIS_MAXIMUM_VALUE_BYTES:1048576}
|
||||
# Optional cache-only L1. Never reuse for session, idempotency or strict rate-limit state.
|
||||
l1:
|
||||
enabled: ${APP_CACHE_REDIS_L1_ENABLED:false}
|
||||
maximum-entries: ${APP_CACHE_REDIS_L1_MAXIMUM_ENTRIES:10000}
|
||||
maximum-weight-bytes: ${APP_CACHE_REDIS_L1_MAXIMUM_WEIGHT_BYTES:67108864}
|
||||
maximum-entry-weight-bytes: ${APP_CACHE_REDIS_L1_MAXIMUM_ENTRY_WEIGHT_BYTES:1048576}
|
||||
time-to-live: ${APP_CACHE_REDIS_L1_TTL:30s}
|
||||
generation-recheck-interval: ${APP_CACHE_REDIS_L1_GENERATION_RECHECK_INTERVAL:5s}
|
||||
invalidation-queue-capacity: ${APP_CACHE_REDIS_L1_INVALIDATION_QUEUE_CAPACITY:1024}
|
||||
# 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).
|
||||
@@ -504,44 +674,3 @@ app:
|
||||
provider: ${APP_NOTIFICATION_SLACK_PROVIDER}
|
||||
email:
|
||||
provider: ${APP_NOTIFICATION_EMAIL_PROVIDER}
|
||||
# ---------------------------------------------------------------------------
|
||||
# feature-outbound-http-client-baseline D5/D3/D7
|
||||
# Registry SSOT: docs/registries/env-keys.yaml (APP_OUTBOUND_HTTP_* rows 489–570)
|
||||
# Bound into OutboundHttpSettings @ConfigurationProperties(prefix = "app.outbound.http").
|
||||
# ---------------------------------------------------------------------------
|
||||
outbound:
|
||||
http:
|
||||
# duration (e.g. 2s). REQUIRED — non-zero (spring_duration_shorthand_non_zero).
|
||||
connect-timeout: ${APP_OUTBOUND_HTTP_CONNECT_TIMEOUT}
|
||||
# duration (e.g. 5s). REQUIRED — non-zero.
|
||||
read-timeout: ${APP_OUTBOUND_HTTP_READ_TIMEOUT}
|
||||
# duration (e.g. 10s). REQUIRED — non-zero (deadline budget for the whole call incl. retries).
|
||||
global-call-timeout: ${APP_OUTBOUND_HTTP_GLOBAL_CALL_TIMEOUT}
|
||||
# Per-client live worker bound; timed-out non-cooperative workers retain a slot until exit.
|
||||
maximum-in-flight-calls: ${APP_OUTBOUND_HTTP_MAXIMUM_IN_FLIGHT_CALLS:128}
|
||||
# true | false (boolean_strict). Resilience4j retry — default disabled (D3).
|
||||
retry-enabled: ${APP_OUTBOUND_HTTP_RETRY_ENABLED:false}
|
||||
# retry 튜닝 (retry-enabled=true 일 때 적용). 기본값 = 기존 하드코딩 동작 보존.
|
||||
retry:
|
||||
# int >= 1 (positive_int). 총 시도 횟수(최초 시도 포함).
|
||||
max-attempts: ${APP_OUTBOUND_HTTP_RETRY_MAX_ATTEMPTS:3}
|
||||
# duration (spring_duration_shorthand_non_zero). exponential backoff 시작 간격.
|
||||
initial-backoff: ${APP_OUTBOUND_HTTP_RETRY_INITIAL_BACKOFF:100ms}
|
||||
# double >= 1.0 (double_ge_1). exponential backoff 배수.
|
||||
backoff-multiplier: ${APP_OUTBOUND_HTTP_RETRY_BACKOFF_MULTIPLIER:2.0}
|
||||
# true | false (boolean_strict). Resilience4j circuit breaker — default disabled.
|
||||
circuit-breaker-enabled: ${APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_ENABLED:false}
|
||||
# circuit-breaker 튜닝 (circuit-breaker-enabled=true 일 때 적용). 기본값 = Resilience4j ofDefaults().
|
||||
circuit-breaker:
|
||||
# float in (0, 100] (float_in_0_exclusive_to_100). open 전환 실패율 임계치(%).
|
||||
failure-rate-threshold: ${APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD:50}
|
||||
# int >= 1 (positive_int). COUNT_BASED sliding window 크기.
|
||||
sliding-window-size: ${APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE:100}
|
||||
# int >= 1 (positive_int). 실패율 계산을 시작하는 최소 호출 수.
|
||||
minimum-number-of-calls: ${APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_MINIMUM_NUMBER_OF_CALLS:100}
|
||||
# duration (spring_duration_shorthand_non_zero). open 상태 유지 시간.
|
||||
wait-duration-in-open-state: ${APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_WAIT_DURATION_IN_OPEN_STATE:60s}
|
||||
# int >= 1 (positive_int). half-open 상태에서 허용하는 시험 호출 수.
|
||||
permitted-calls-in-half-open: ${APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_PERMITTED_CALLS_IN_HALF_OPEN:10}
|
||||
# data size (e.g. 10MB). Streaming threshold — buffered reads above this fail (D7).
|
||||
response-size-limit: ${APP_OUTBOUND_HTTP_RESPONSE_SIZE_LIMIT:10MB}
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package dev.caskeleton.adapter.outbound.cache.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettingsFactory;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.config.RedisRole;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.config.RedisRoleBinding;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.runtime.RedisClientRuntimeSettings;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Capability;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
class RedisCoordinationRuntimeCompositionContractTest {
|
||||
|
||||
private static final RedisClientRuntimeSettings CLIENT_SETTINGS =
|
||||
new RedisClientRuntimeSettings(
|
||||
"composition-test",
|
||||
Duration.ofMillis(100),
|
||||
Duration.ofMillis(100),
|
||||
Duration.ofMillis(200),
|
||||
Duration.ofMillis(500),
|
||||
Duration.ofMillis(300),
|
||||
8,
|
||||
3,
|
||||
Duration.ofSeconds(5));
|
||||
|
||||
@Test
|
||||
void twoCoordinationCapabilitiesCreateOneRuntimeAndOneRouter() {
|
||||
MockEnvironment environment =
|
||||
new MockEnvironment()
|
||||
.withProperty("ca-skeleton.capabilities.rate-limit.provider", "redis")
|
||||
.withProperty("ca-skeleton.capabilities.idempotency.provider", "redis");
|
||||
Map<RedisRole, Set<Capability>> capabilities =
|
||||
RedisCanonicalConfig.selectedCapabilities(environment);
|
||||
AtomicInteger runtimeBuilds = new AtomicInteger();
|
||||
|
||||
try (RedisCanonicalRoleRegistry registry =
|
||||
new RedisCanonicalRoleRegistry(
|
||||
new RedisDeploymentSettingsFactory()
|
||||
.compileActive(declaredProvider(), selectedRoles(capabilities)),
|
||||
CLIENT_SETTINGS,
|
||||
4,
|
||||
16_384,
|
||||
1_048_576,
|
||||
Duration.ofSeconds(1),
|
||||
Duration.ofMinutes(5),
|
||||
deployment -> {
|
||||
runtimeBuilds.incrementAndGet();
|
||||
return new FakeRuntime(deployment.deploymentId());
|
||||
},
|
||||
declaredProvider().roles(),
|
||||
capabilities,
|
||||
java.time.Clock.systemUTC())) {
|
||||
|
||||
assertThat(capabilities.get(RedisRole.COORDINATION))
|
||||
.containsExactlyInAnyOrder(Capability.RATE_LIMIT, Capability.IDEMPOTENCY);
|
||||
assertThat(registry.boundRoles()).containsExactly(RedisRole.COORDINATION);
|
||||
assertThat(runtimeBuilds).hasValue(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<RedisRole> selectedRoles(Map<RedisRole, Set<Capability>> capabilities) {
|
||||
return capabilities.entrySet().stream()
|
||||
.filter(entry -> !entry.getValue().isEmpty())
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(java.util.stream.Collectors.toUnmodifiableSet());
|
||||
}
|
||||
|
||||
private static RedisProviderSettings declaredProvider() {
|
||||
return new RedisProviderSettings(
|
||||
Map.of(
|
||||
"cache-main", standalone("cache-main"),
|
||||
"coord-main", standalone("coord-main"),
|
||||
"session-main", standalone("session-main")),
|
||||
Map.of(
|
||||
RedisRole.CACHE, new RedisRoleBinding("cache-main", false, "allkeys-lfu"),
|
||||
RedisRole.COORDINATION, new RedisRoleBinding("coord-main", true, "noeviction"),
|
||||
RedisRole.SESSION, new RedisRoleBinding("session-main", true, "noeviction")));
|
||||
}
|
||||
|
||||
private static RedisProviderSettings.DeploymentProperties standalone(String deploymentId) {
|
||||
return new RedisProviderSettings.DeploymentProperties(
|
||||
RedisProviderSettings.Topology.STANDALONE,
|
||||
new RedisProviderSettings.StandaloneProperties(
|
||||
List.of(
|
||||
new RedisProviderSettings.EndpointProperties(deploymentId + ".internal", 6379))),
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
new RedisProviderSettings.AuthenticationProperties(
|
||||
"runtime", "secret://environment/REDIS_PASSWORD"),
|
||||
new RedisProviderSettings.TlsProperties(
|
||||
true, true, "secret://environment/REDIS_TRUST_PEM"));
|
||||
}
|
||||
|
||||
private static final class FakeRuntime implements RedisRoutableCommandRuntime {
|
||||
|
||||
private final String deploymentId;
|
||||
private final Map<RedisPhysicalKey, byte[]> values = new java.util.HashMap<>();
|
||||
|
||||
private FakeRuntime(String deploymentId) {
|
||||
this.deploymentId = deploymentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void probe(Duration timeout) {}
|
||||
|
||||
@Override
|
||||
public String deploymentId() {
|
||||
return deploymentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] get(RedisPhysicalKey key) {
|
||||
byte[] value = values.get(key);
|
||||
return value == null ? null : value.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(RedisPhysicalKey key, RedisBinaryValue value, Duration timeToLive) {
|
||||
values.put(key, value.copyEncoded());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long delete(RedisPhysicalKey key) {
|
||||
return values.remove(key) == null ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RedisCatalogProgramReply executeCatalogProgram(
|
||||
RedisCatalogProgramInvocation invocation) {
|
||||
RedisProgramId programId = invocation.programIdOrNull();
|
||||
if (programId == null) {
|
||||
return RedisCatalogProgramReply.value(
|
||||
"ACL_OK".getBytes(java.nio.charset.StandardCharsets.US_ASCII));
|
||||
}
|
||||
return switch (programId) {
|
||||
case RATE_FIXED_WINDOW_V2 ->
|
||||
RedisCatalogProgramReply.multi(
|
||||
ascii("STATE_INCOMPATIBLE", "NONE", "1", "1", "1", "0", "0", "0"));
|
||||
case IDEMPOTENCY_CLAIM_V1 ->
|
||||
RedisCatalogProgramReply.multi(ascii("STATE_INCOMPATIBLE", "0", "0", "-", "-", "-"));
|
||||
default -> throw new AssertionError("unexpected semantic program " + programId);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String loadCatalogProgram(RedisCatalogProgramInvocation invocation) {
|
||||
return invocation.sha1();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {}
|
||||
|
||||
private static List<byte[]> ascii(String... fields) {
|
||||
return java.util.Arrays.stream(fields)
|
||||
.map(field -> field.getBytes(java.nio.charset.StandardCharsets.US_ASCII))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package dev.caskeleton.adapter.outbound.cache.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.DestroyableRedisSecret;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial;
|
||||
import dev.caskeleton.bootstrap.runtime.redis.RedisHealthContributorConfig;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.health.contributor.HealthIndicator;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
class RedisOptionalCacheColdStartCompositionTest {
|
||||
|
||||
@Test
|
||||
void selectedOptionalCacheStartsItsRuntimeCacheBeanAndDegradedHealthOnTypedTransientOutage() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
RedisCanonicalConfig.class,
|
||||
RedisCanonicalCacheConfig.class,
|
||||
RedisHealthContributorConfig.class)
|
||||
.withBean(
|
||||
RedisRuntimeConnector.class,
|
||||
() ->
|
||||
deployment -> {
|
||||
throw new RedisTemporaryConnectionException();
|
||||
})
|
||||
.withBean(
|
||||
RedisCredentialMaterialProvider.class,
|
||||
RedisOptionalCacheColdStartCompositionTest::secretProvider)
|
||||
.withPropertyValues(optionalCacheProperties())
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasBean("redisCanonicalRoleRegistry");
|
||||
assertThat(context).hasBean("redisCanonicalDefaultCacheRegion");
|
||||
assertThat(context).hasBean("redisOptional");
|
||||
assertThat(context).doesNotHaveBean("redisRequired");
|
||||
assertThat(
|
||||
context.getBean("redisOptional", HealthIndicator.class).health().toString())
|
||||
.contains("DEGRADED", "COMMAND_UNAVAILABLE")
|
||||
.doesNotContain("cache.internal");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void requiredTransientAndOptionalPermanentConnectorFailuresStillFailTheContext() {
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(RedisCanonicalConfig.class, RedisHealthContributorConfig.class)
|
||||
.withBean(
|
||||
RedisRuntimeConnector.class,
|
||||
() ->
|
||||
deployment -> {
|
||||
throw new RedisTemporaryConnectionException();
|
||||
})
|
||||
.withPropertyValues(requiredCoordinationProperties())
|
||||
.run(context -> assertThat(context).hasFailed());
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(RedisCanonicalConfig.class, RedisHealthContributorConfig.class)
|
||||
.withBean(
|
||||
RedisRuntimeConnector.class,
|
||||
() ->
|
||||
deployment -> {
|
||||
throw new IllegalStateException("permanent authentication failure");
|
||||
})
|
||||
.withPropertyValues(optionalCacheProperties())
|
||||
.run(context -> assertThat(context).hasFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateRuntimeConnectorSeamsFailClosedInsteadOfChoosingSilently() {
|
||||
RedisRuntimeConnector first =
|
||||
deployment -> {
|
||||
throw new RedisTemporaryConnectionException();
|
||||
};
|
||||
RedisRuntimeConnector second =
|
||||
deployment -> {
|
||||
throw new RedisTemporaryConnectionException();
|
||||
};
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(RedisCanonicalConfig.class)
|
||||
.withBean("firstRedisRuntimeConnector", RedisRuntimeConnector.class, () -> first)
|
||||
.withBean("secondRedisRuntimeConnector", RedisRuntimeConnector.class, () -> second)
|
||||
.withPropertyValues(optionalCacheProperties())
|
||||
.run(context -> assertThat(context).hasFailed());
|
||||
}
|
||||
|
||||
private static RedisCredentialMaterialProvider secretProvider() {
|
||||
byte[] raw = new byte[32];
|
||||
java.util.Arrays.fill(raw, (byte) 7);
|
||||
char[] encoded = Base64.getEncoder().encodeToString(raw).toCharArray();
|
||||
java.util.Arrays.fill(raw, (byte) 0);
|
||||
return reference ->
|
||||
new VersionedRedisCredentialMaterial(
|
||||
"composition-v1",
|
||||
Instant.parse("2030-01-01T00:00:00Z"),
|
||||
DestroyableRedisSecret.from(encoded));
|
||||
}
|
||||
|
||||
private static String[] optionalCacheProperties() {
|
||||
return new String[] {
|
||||
"ca-skeleton.capabilities.cache.bindings.default=redis",
|
||||
"ca-skeleton.capabilities.cache.regions.default.key-hmac-secret-reference=secret://environment/CACHE_KEY_HMAC",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.topology=standalone",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.standalone.endpoints[0].host=cache.internal",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.standalone.endpoints[0].port=6379",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.authentication.username=cache-runtime",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.authentication.password-reference=secret://environment/CACHE_PASSWORD",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.tls.enabled=true",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.tls.verify-hostname=true",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.tls.trust-bundle-reference=secret://environment/CACHE_TRUST",
|
||||
"ca-skeleton.providers.redis.roles.cache.deployment-id=cache-main",
|
||||
"ca-skeleton.providers.redis.roles.cache.required=false",
|
||||
"ca-skeleton.providers.redis.roles.cache.expected-eviction=allkeys-lfu"
|
||||
};
|
||||
}
|
||||
|
||||
private static String[] requiredCoordinationProperties() {
|
||||
return new String[] {
|
||||
"ca-skeleton.capabilities.rate-limit.provider=redis",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.topology=standalone",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.standalone.endpoints[0].host=coord.internal",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.standalone.endpoints[0].port=6379",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.authentication.username=coord-runtime",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.authentication.password-reference=secret://environment/COORD_PASSWORD",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.tls.enabled=true",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.tls.verify-hostname=true",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.tls.trust-bundle-reference=secret://environment/COORD_TRUST",
|
||||
"ca-skeleton.providers.redis.roles.coordination.deployment-id=coord-main",
|
||||
"ca-skeleton.providers.redis.roles.coordination.required=true",
|
||||
"ca-skeleton.providers.redis.roles.coordination.expected-eviction=noeviction"
|
||||
};
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.adapter.inbound.web.auth.RedisSessionWebConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCacheAdapterConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalCacheConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisEfficiencyLeaseConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisIdempotencyConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisRateLimitConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisSessionConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.config.RedisRole;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisTrustMaterialProvider;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStorePortV2;
|
||||
import dev.caskeleton.application.lease.DistributedLeasePort;
|
||||
import dev.caskeleton.bootstrap.runtime.redis.RedisHealthContributorConfig;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Capability;
|
||||
import dev.caskeleton.shared.ratelimit.EdgeRateLimitPort;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
|
||||
class RedisCanonicalCompositionContractTest {
|
||||
|
||||
@Test
|
||||
void everyRedisSelectorActivatesOnlyItsCanonicalRole() {
|
||||
Map<String, ExpectedActivation> selections =
|
||||
Map.of(
|
||||
"ca-skeleton.capabilities.cache.bindings.default=redis",
|
||||
new ExpectedActivation(RedisRole.CACHE, Capability.CACHE),
|
||||
"ca-skeleton.capabilities.rate-limit.provider=redis",
|
||||
new ExpectedActivation(RedisRole.COORDINATION, Capability.RATE_LIMIT),
|
||||
"ca-skeleton.capabilities.idempotency.provider=redis",
|
||||
new ExpectedActivation(RedisRole.COORDINATION, Capability.IDEMPOTENCY),
|
||||
"ca-skeleton.capabilities.lease.provider=redis",
|
||||
new ExpectedActivation(RedisRole.COORDINATION, Capability.EFFICIENCY_LEASE),
|
||||
"ca-skeleton.security.auth-mode=redis-session",
|
||||
new ExpectedActivation(RedisRole.SESSION, Capability.SESSION));
|
||||
|
||||
selections.forEach(
|
||||
(selector, expected) -> {
|
||||
MockEnvironment environment = declaredRoleEnvironment();
|
||||
String[] selectorParts = selector.split("=", 2);
|
||||
environment.setProperty(selectorParts[0], selectorParts[1]);
|
||||
|
||||
Map<RedisRole, Set<Capability>> active =
|
||||
RedisCanonicalConfig.selectedCapabilities(environment);
|
||||
|
||||
assertThat(active.get(expected.role())).containsExactly(expected.capability());
|
||||
active.forEach(
|
||||
(role, capabilities) -> {
|
||||
if (role != expected.role()) {
|
||||
assertThat(capabilities).isEmpty();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void declaredRolesRemainFullyInertUntilACapabilitySelectsRedis() {
|
||||
AtomicInteger credentialResolutions = new AtomicInteger();
|
||||
AtomicInteger trustResolutions = new AtomicInteger();
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
RedisCanonicalConfig.class,
|
||||
RedisCanonicalCacheConfig.class,
|
||||
RedisCacheAdapterConfig.class,
|
||||
RedisEfficiencyLeaseConfig.class,
|
||||
RedisIdempotencyConfig.class,
|
||||
RedisRateLimitConfig.class,
|
||||
RedisSessionConfig.class,
|
||||
RedisSessionWebConfig.class,
|
||||
RedisHealthContributorConfig.class)
|
||||
.withBean(
|
||||
RedisCredentialMaterialProvider.class,
|
||||
() ->
|
||||
reference -> {
|
||||
credentialResolutions.incrementAndGet();
|
||||
throw new AssertionError("unselected Redis role resolved credential material");
|
||||
})
|
||||
.withBean(
|
||||
RedisTrustMaterialProvider.class,
|
||||
() ->
|
||||
reference -> {
|
||||
trustResolutions.incrementAndGet();
|
||||
throw new AssertionError("unselected Redis role resolved trust material");
|
||||
})
|
||||
.withPropertyValues(disabledCapabilityProperties())
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context.getBean(RedisHealthSnapshotProvider.class).snapshot().roles())
|
||||
.isEmpty();
|
||||
assertThat(context.getBeansOfType(DistributedLeasePort.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(IdempotencyStorePortV2.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(EdgeRateLimitPort.class)).isEmpty();
|
||||
assertThat(context)
|
||||
.doesNotHaveBean("redisCanonicalDefaultCacheRegion")
|
||||
.doesNotHaveBean("redisCanonicalDefaultCacheInvalidationSubscription")
|
||||
.doesNotHaveBean("redisLuaVersionedSessionStore")
|
||||
.doesNotHaveBean("redisVersionedSessionRepository")
|
||||
.doesNotHaveBean("springSessionRepositoryFilter")
|
||||
.doesNotHaveBean("redisRequired")
|
||||
.doesNotHaveBean("redisOptional");
|
||||
assertThat(context.getBeanFactory().getBeanDefinitionNames())
|
||||
.allSatisfy(
|
||||
beanName -> {
|
||||
Class<?> beanType = context.getBeanFactory().getType(beanName, false);
|
||||
assertThat(beanType == null ? "" : beanType.getName())
|
||||
.doesNotStartWith("io.lettuce.");
|
||||
});
|
||||
assertThat(credentialResolutions).hasValue(0);
|
||||
assertThat(trustResolutions).hasValue(0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void unboundProviderDefinitionsResolveNoMaterialAndOpenNoNativeClient() {
|
||||
AtomicInteger credentialResolutions = new AtomicInteger();
|
||||
AtomicInteger trustResolutions = new AtomicInteger();
|
||||
RedisCredentialMaterialProvider credentialProvider =
|
||||
reference -> {
|
||||
credentialResolutions.incrementAndGet();
|
||||
throw new AssertionError("unbound Redis deployment resolved credential material");
|
||||
};
|
||||
RedisTrustMaterialProvider trustProvider =
|
||||
reference -> {
|
||||
trustResolutions.incrementAndGet();
|
||||
throw new AssertionError("unbound Redis deployment resolved trust material");
|
||||
};
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(
|
||||
RedisCanonicalConfig.class,
|
||||
RedisEfficiencyLeaseConfig.class,
|
||||
RedisIdempotencyConfig.class,
|
||||
RedisRateLimitConfig.class)
|
||||
.withBean(RedisCredentialMaterialProvider.class, () -> credentialProvider)
|
||||
.withBean(RedisTrustMaterialProvider.class, () -> trustProvider)
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.providers.redis.deployments.unused.topology=standalone",
|
||||
"ca-skeleton.providers.redis.deployments.unused.standalone.endpoints[0].host=unused.invalid",
|
||||
"ca-skeleton.providers.redis.deployments.unused.standalone.endpoints[0].port=6379",
|
||||
"ca-skeleton.providers.redis.deployments.unused.database=0",
|
||||
"ca-skeleton.providers.redis.deployments.unused.authentication.username=unused-runtime",
|
||||
"ca-skeleton.providers.redis.deployments.unused.authentication.password-reference=secret://environment/APP_CACHE_REDIS_PASSWORD",
|
||||
"ca-skeleton.providers.redis.deployments.unused.tls.enabled=true",
|
||||
"ca-skeleton.providers.redis.deployments.unused.tls.verify-hostname=true",
|
||||
"ca-skeleton.providers.redis.deployments.unused.tls.trust-bundle-reference=secret://environment/APP_CACHE_REDIS_TRUST_PEM")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasBean("redisCanonicalRoleRegistry");
|
||||
assertThat(context.getBeansOfType(DistributedLeasePort.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(IdempotencyStorePortV2.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(EdgeRateLimitPort.class)).isEmpty();
|
||||
assertThat(context.getBeanFactory().getBeanDefinitionNames())
|
||||
.allSatisfy(
|
||||
beanName -> {
|
||||
Class<?> beanType = context.getBeanFactory().getType(beanName, false);
|
||||
assertThat(beanType == null ? "" : beanType.getName())
|
||||
.doesNotStartWith("io.lettuce.");
|
||||
});
|
||||
assertThat(credentialResolutions).hasValue(0);
|
||||
assertThat(trustResolutions).hasValue(0);
|
||||
});
|
||||
}
|
||||
|
||||
private static String[] disabledCapabilityProperties() {
|
||||
return new String[] {
|
||||
"ca-skeleton.capabilities.cache.bindings.default=disabled",
|
||||
"ca-skeleton.capabilities.rate-limit.provider=disabled",
|
||||
"ca-skeleton.capabilities.idempotency.provider=jdbc",
|
||||
"ca-skeleton.capabilities.lease.provider=disabled",
|
||||
"ca-skeleton.security.auth-mode=jwt",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.topology=standalone",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.standalone.endpoints[0].host=cache.internal",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.standalone.endpoints[0].port=6379",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.database=0",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.authentication.username=cache-runtime",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.authentication.password-reference=secret://environment/APP_CACHE_REDIS_PASSWORD",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.tls.enabled=true",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.tls.verify-hostname=true",
|
||||
"ca-skeleton.providers.redis.deployments.cache-main.tls.trust-bundle-reference=secret://environment/APP_CACHE_REDIS_TRUST_PEM",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.topology=standalone",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.standalone.endpoints[0].host=coord.internal",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.standalone.endpoints[0].port=6379",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.database=0",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.authentication.username=coord-runtime",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.authentication.password-reference=secret://environment/APP_RATE_LIMIT_REDIS_PASSWORD",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.tls.enabled=true",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.tls.verify-hostname=true",
|
||||
"ca-skeleton.providers.redis.deployments.coord-main.tls.trust-bundle-reference=secret://environment/APP_RATE_LIMIT_REDIS_TRUST_PEM",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.topology=standalone",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.standalone.endpoints[0].host=session.internal",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.standalone.endpoints[0].port=6379",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.database=0",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.authentication.username=session-runtime",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.authentication.password-reference=secret://environment/APP_SESSION_REDIS_PASSWORD",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.tls.enabled=true",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.tls.verify-hostname=true",
|
||||
"ca-skeleton.providers.redis.deployments.session-main.tls.trust-bundle-reference=secret://environment/APP_SESSION_REDIS_TRUST_PEM",
|
||||
"ca-skeleton.providers.redis.roles.cache.deployment-id=cache-main",
|
||||
"ca-skeleton.providers.redis.roles.cache.required=false",
|
||||
"ca-skeleton.providers.redis.roles.cache.expected-eviction=allkeys-lfu",
|
||||
"ca-skeleton.providers.redis.roles.coordination.deployment-id=coord-main",
|
||||
"ca-skeleton.providers.redis.roles.coordination.required=true",
|
||||
"ca-skeleton.providers.redis.roles.coordination.expected-eviction=noeviction",
|
||||
"ca-skeleton.providers.redis.roles.session.deployment-id=session-main",
|
||||
"ca-skeleton.providers.redis.roles.session.required=true",
|
||||
"ca-skeleton.providers.redis.roles.session.expected-eviction=noeviction"
|
||||
};
|
||||
}
|
||||
|
||||
private static MockEnvironment declaredRoleEnvironment() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
for (String property : disabledCapabilityProperties()) {
|
||||
String[] parts = property.split("=", 2);
|
||||
environment.setProperty(parts[0], parts[1]);
|
||||
}
|
||||
return environment;
|
||||
}
|
||||
|
||||
private record ExpectedActivation(RedisRole role, Capability capability) {}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
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.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class RedisCiAggregatorContractTest {
|
||||
|
||||
private static final Set<String> BLOCKING_JOBS =
|
||||
Set.of("quality-gates", "sample-off", "gate-matrix-lint", "redis-standalone");
|
||||
|
||||
@Test
|
||||
void releaseAggregatorNeedsAndChecksEveryBlockingJob() throws IOException {
|
||||
String workflow =
|
||||
Files.readString(repositoryRoot().resolve(".github/workflows/ci-quality-gates.yml"));
|
||||
String releaseGate = jobBody(workflow, "release-gate");
|
||||
|
||||
assertThat(needs(releaseGate)).containsExactlyInAnyOrderElementsOf(BLOCKING_JOBS);
|
||||
assertThat(releaseGate)
|
||||
.contains("QUALITY_RESULT: ${{ needs.quality-gates.result }}")
|
||||
.contains("SAMPLE_OFF_RESULT: ${{ needs.sample-off.result }}")
|
||||
.contains("MATRIX_RESULT: ${{ needs.gate-matrix-lint.result }}")
|
||||
.contains("REDIS_RESULT: ${{ needs.redis-standalone.result }}")
|
||||
.contains("\"${REDIS_RESULT}\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void readinessWorkflowUsesStrictGradleMatrixAndReconcilesSanitizedEvidence() throws IOException {
|
||||
String workflow =
|
||||
Files.readString(
|
||||
repositoryRoot().resolve(".github/workflows/redis-production-readiness.yml"));
|
||||
String resolver = jobBody(workflow, "resolve-redis-readiness");
|
||||
|
||||
assertThat(resolver)
|
||||
.contains("./gradlew writeRedisCiMatrix")
|
||||
.contains("redis-readiness-matrix.json")
|
||||
.doesNotContain("registry.read_text");
|
||||
|
||||
for (String jobId :
|
||||
Set.of(
|
||||
"redis-security",
|
||||
"redis-sentinel",
|
||||
"redis-cluster",
|
||||
"redis-fault",
|
||||
"redis-compatibility",
|
||||
"selected-card-readiness",
|
||||
"redis-all-candidates")) {
|
||||
assertThat(jobBody(workflow, jobId))
|
||||
.as("sanitized evidence upload for %s", jobId)
|
||||
.contains("id: redis-tests")
|
||||
.contains("id: redis-evidence-sanitizer")
|
||||
.contains("if: always()")
|
||||
.contains("steps.redis-evidence-sanitizer.outcome == 'success'")
|
||||
.contains("build/redis-evidence")
|
||||
.contains("if-no-files-found: error")
|
||||
.doesNotContain("build/test-results")
|
||||
.doesNotContain("build/reports/tests")
|
||||
.doesNotContain("container logs");
|
||||
}
|
||||
|
||||
assertThat(jobBody(workflow, "selected-card-readiness"))
|
||||
.contains("name: redis-selected-${{ matrix.cardId }}");
|
||||
for (String jobId :
|
||||
Set.of(
|
||||
"resolve-redis-readiness",
|
||||
"redis-security",
|
||||
"redis-sentinel",
|
||||
"redis-cluster",
|
||||
"redis-fault",
|
||||
"redis-compatibility",
|
||||
"redis-all-candidates",
|
||||
"redis-production-readiness")) {
|
||||
assertThat(jobBody(workflow, jobId))
|
||||
.as("selected artifact name ownership for %s", jobId)
|
||||
.doesNotContain("name: redis-selected-");
|
||||
}
|
||||
String readiness = jobBody(workflow, "redis-production-readiness");
|
||||
assertThat(readiness)
|
||||
.contains("if: ${{ always() && needs.resolve-redis-readiness.result == 'success' }}")
|
||||
.contains("Require the exact selected matrix result")
|
||||
.contains("selected-card-readiness result mismatch")
|
||||
.contains("Record the downloaded selected artifact inventory")
|
||||
.contains("downloaded selected artifact inventory mismatch")
|
||||
.contains("redis-ci-result.json")
|
||||
.contains("actions/download-artifact")
|
||||
.contains("name: redis-readiness-control")
|
||||
.contains("pattern: redis-selected-*")
|
||||
.contains("verifyRedisSelectedEvidenceArtifacts")
|
||||
.contains("redisProductionReadiness")
|
||||
.contains("-PredisCiResultFile=")
|
||||
.contains("needs.resolve-redis-readiness.outputs.selected_count == '0'")
|
||||
.contains("expected_result = \"skipped\" if selected_count == 0 else \"success\"")
|
||||
.contains("needs.resolve-redis-readiness.outputs.selected_count != '0'")
|
||||
.contains("needs.selected-card-readiness.result == 'success'");
|
||||
}
|
||||
|
||||
private static Set<String> needs(String job) {
|
||||
Set<String> result = new LinkedHashSet<>();
|
||||
boolean inNeeds = false;
|
||||
for (String line : job.lines().toList()) {
|
||||
if (line.equals(" needs:")) {
|
||||
inNeeds = true;
|
||||
continue;
|
||||
}
|
||||
if (inNeeds && line.matches(" - [a-z0-9-]+")) {
|
||||
result.add(line.substring(line.indexOf('-') + 1).trim());
|
||||
} else if (inNeeds && !line.isBlank()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String jobBody(String workflow, String jobId) {
|
||||
Pattern pattern =
|
||||
Pattern.compile(
|
||||
"(?ms)^ " + Pattern.quote(jobId) + ":\\n(.*?)(?=^ [a-zA-Z0-9_-]+:\\n|\\z)");
|
||||
Matcher matcher = pattern.matcher(workflow);
|
||||
assertThat(matcher.find()).as("workflow job %s", jobId).isTrue();
|
||||
return " " + jobId + ":\n" + matcher.group(1);
|
||||
}
|
||||
|
||||
private static Path repositoryRoot() {
|
||||
Path current = Path.of("").toAbsolutePath().normalize();
|
||||
while (current != null && !Files.isDirectory(current.resolve(".github/workflows"))) {
|
||||
current = current.getParent();
|
||||
}
|
||||
if (current == null) {
|
||||
throw new IllegalStateException("repository root containing .github/workflows was not found");
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.adapter.inbound.web.auth.RedisSessionWebConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCacheAdapterConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalCacheConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisEfficiencyLeaseConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisIdempotencyConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisRateLimitConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisSessionConfig;
|
||||
import dev.caskeleton.bootstrap.runtime.redis.RedisHealthContributorConfig;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.core.env.MapPropertySource;
|
||||
import org.springframework.core.env.PropertySource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.yaml.snakeyaml.LoaderOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.SafeConstructor;
|
||||
|
||||
class RedisDefaultActivationContractTest {
|
||||
|
||||
@Test
|
||||
void shippedLocalEnvironmentDoesNotEnableTransportWithoutAProvider() throws IOException {
|
||||
Path root = repositoryRoot();
|
||||
Map<String, String> environment =
|
||||
Files.readAllLines(root.resolve("src/.env")).stream()
|
||||
.filter(line -> line.matches("[A-Z][A-Z0-9_]*=.*"))
|
||||
.map(line -> line.split("=", 2))
|
||||
.collect(Collectors.toMap(parts -> parts[0], parts -> parts[1]));
|
||||
|
||||
assertThat(environment)
|
||||
.containsEntry("APP_RATE_LIMIT_ENABLED", "false")
|
||||
.containsEntry("APP_RATE_LIMIT_PROVIDER", "disabled");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shippedConfigurationParsesUniquelyAndBootsWithoutRedisActivation() throws IOException {
|
||||
Path root = repositoryRoot();
|
||||
Map<String, String> environment = shippedEnvironment(root);
|
||||
Map<String, Object> environmentProperties = new LinkedHashMap<>(environment);
|
||||
Path applicationYaml = root.resolve("src/app-bootstrap/src/main/resources/application.yml");
|
||||
Map<String, Object> application = parsedYaml(applicationYaml);
|
||||
List<PropertySource<?>> configuration =
|
||||
new YamlPropertySourceLoader()
|
||||
.load("shipped-application", new FileSystemResource(applicationYaml));
|
||||
|
||||
Map<String, Object> caSkeleton = child(application, "ca-skeleton");
|
||||
Map<String, Object> capabilities = child(caSkeleton, "capabilities");
|
||||
Map<String, Object> rateLimit = child(capabilities, "rate-limit");
|
||||
assertThat(caSkeleton).doesNotContainKey("rate-limit");
|
||||
assertThat(rateLimit)
|
||||
.containsEntry("provider", "${APP_RATE_LIMIT_PROVIDER:disabled}")
|
||||
.doesNotContainKey("algorithm");
|
||||
|
||||
new ApplicationContextRunner()
|
||||
.withInitializer(
|
||||
context -> {
|
||||
for (PropertySource<?> source : configuration) {
|
||||
context.getEnvironment().getPropertySources().addLast(source);
|
||||
}
|
||||
context
|
||||
.getEnvironment()
|
||||
.getPropertySources()
|
||||
.addFirst(new MapPropertySource("shipped-env", environmentProperties));
|
||||
})
|
||||
.withUserConfiguration(
|
||||
RedisCanonicalConfig.class,
|
||||
RedisCanonicalCacheConfig.class,
|
||||
RedisCacheAdapterConfig.class,
|
||||
RedisEfficiencyLeaseConfig.class,
|
||||
RedisIdempotencyConfig.class,
|
||||
RedisRateLimitConfig.class,
|
||||
RedisSessionConfig.class,
|
||||
RedisSessionWebConfig.class,
|
||||
RedisHealthContributorConfig.class)
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context.getBean(RedisHealthSnapshotProvider.class).snapshot().roles())
|
||||
.isEmpty();
|
||||
assertThat(context)
|
||||
.doesNotHaveBean("redisCanonicalDefaultCacheRegion")
|
||||
.doesNotHaveBean("redisCanonicalDefaultCacheInvalidationSubscription")
|
||||
.doesNotHaveBean("distributedRateLimiter")
|
||||
.doesNotHaveBean("redisIdempotencyStoreV2")
|
||||
.doesNotHaveBean("distributedLeasePort")
|
||||
.doesNotHaveBean("redisLuaVersionedSessionStore")
|
||||
.doesNotHaveBean("redisVersionedSessionRepository")
|
||||
.doesNotHaveBean("springSessionRepositoryFilter")
|
||||
.doesNotHaveBean("redisRequired")
|
||||
.doesNotHaveBean("redisOptional");
|
||||
});
|
||||
}
|
||||
|
||||
private static Map<String, String> shippedEnvironment(Path root) throws IOException {
|
||||
return Files.readAllLines(root.resolve("src/.env")).stream()
|
||||
.filter(line -> line.matches("[A-Z][A-Z0-9_]*=.*"))
|
||||
.map(line -> line.split("=", 2))
|
||||
.collect(Collectors.toMap(parts -> parts[0], parts -> parts[1]));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> parsedYaml(Path path) throws IOException {
|
||||
LoaderOptions options = new LoaderOptions();
|
||||
options.setAllowDuplicateKeys(false);
|
||||
try (var reader = Files.newBufferedReader(path)) {
|
||||
return (Map<String, Object>) new Yaml(new SafeConstructor(options)).load(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> child(Map<String, Object> parent, String key) {
|
||||
assertThat(parent).containsKey(key);
|
||||
return (Map<String, Object>) parent.get(key);
|
||||
}
|
||||
|
||||
private static Path repositoryRoot() {
|
||||
Path current = Path.of("").toAbsolutePath().normalize();
|
||||
while (current != null && !Files.isDirectory(current.resolve(".github/workflows"))) {
|
||||
current = current.getParent();
|
||||
}
|
||||
if (current == null) {
|
||||
throw new IllegalStateException("repository root containing .github/workflows was not found");
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.persistence.idempotency.IdempotencyReaper;
|
||||
import dev.caskeleton.adapter.outbound.persistence.idempotency.IdempotencyStoreAdapter;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyClaimAttempt;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyClaimOutcome;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyClaimRequest;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyCompleteOutcome;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyExecutor;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyExecutorV2;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyFailOutcome;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyFailureDisposition;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyInspection;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyInspectionRequest;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyOwner;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyRecord;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyReleaseOutcome;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyRenewOutcome;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyScope;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStartOutcome;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStorePort;
|
||||
import dev.caskeleton.application.idempotency.IdempotencyStorePortV2;
|
||||
import dev.caskeleton.application.idempotency.RequestFingerprint;
|
||||
import dev.caskeleton.application.idempotency.StoredResponse;
|
||||
import dev.caskeleton.bootstrap.idempotency.IdempotencyProviderSelectionConfig;
|
||||
import java.time.Clock;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
class RedisIdempotencyProviderSelectionContractTest {
|
||||
|
||||
private final ApplicationContextRunner runner =
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(IdempotencyProviderSelectionConfig.class);
|
||||
|
||||
@Test
|
||||
void jdbcAndRedisModesEachRequireExactlyTheirOwnVersionedPair() {
|
||||
IdempotencyStorePort jdbcStore = new NoOpJdbcStore();
|
||||
runner
|
||||
.withPropertyValues("ca-skeleton.capabilities.idempotency.provider=jdbc")
|
||||
.withBean(IdempotencyStorePort.class, () -> jdbcStore)
|
||||
.withBean(
|
||||
IdempotencyExecutor.class,
|
||||
() -> new IdempotencyExecutor(jdbcStore, Clock.systemUTC(), Duration.ofHours(1)))
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasSingleBean(IdempotencyStorePort.class);
|
||||
assertThat(context.getBeansOfType(IdempotencyStorePortV2.class)).isEmpty();
|
||||
});
|
||||
|
||||
IdempotencyStorePortV2 redisStore = new NoOpRedisStore();
|
||||
runner
|
||||
.withPropertyValues("ca-skeleton.capabilities.idempotency.provider=redis")
|
||||
.withBean(IdempotencyStorePortV2.class, () -> redisStore)
|
||||
.withBean(
|
||||
IdempotencyExecutorV2.class,
|
||||
() ->
|
||||
new IdempotencyExecutorV2(
|
||||
redisStore,
|
||||
Duration.ofSeconds(30),
|
||||
Duration.ofHours(1),
|
||||
Duration.ofHours(1),
|
||||
"json-v2",
|
||||
"policy-v2"))
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasSingleBean(IdempotencyStorePortV2.class);
|
||||
assertThat(context.getBeansOfType(IdempotencyStorePort.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void duplicateCrossVersionProvidersAndUnknownSelectorFailFast() {
|
||||
IdempotencyStorePort jdbcStore = new NoOpJdbcStore();
|
||||
IdempotencyStorePortV2 redisStore = new NoOpRedisStore();
|
||||
runner
|
||||
.withPropertyValues("ca-skeleton.capabilities.idempotency.provider=redis")
|
||||
.withBean(IdempotencyStorePort.class, () -> jdbcStore)
|
||||
.withBean(
|
||||
IdempotencyExecutor.class,
|
||||
() -> new IdempotencyExecutor(jdbcStore, Clock.systemUTC(), Duration.ofHours(1)))
|
||||
.withBean(IdempotencyStorePortV2.class, () -> redisStore)
|
||||
.withBean(
|
||||
IdempotencyExecutorV2.class,
|
||||
() ->
|
||||
new IdempotencyExecutorV2(
|
||||
redisStore,
|
||||
Duration.ofSeconds(30),
|
||||
Duration.ofHours(1),
|
||||
Duration.ofHours(1),
|
||||
"json-v2",
|
||||
"policy-v2"))
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessageContaining("ambiguous");
|
||||
});
|
||||
|
||||
runner
|
||||
.withPropertyValues("ca-skeleton.capabilities.idempotency.provider=jdbc-and-redis")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getMessage())
|
||||
.contains("ca-skeleton.capabilities.idempotency");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void jpaV1StoreAndReaperAreConditionedOnTheExactJdbcMode() {
|
||||
assertJdbcCondition(IdempotencyStoreAdapter.class);
|
||||
assertJdbcCondition(IdempotencyReaper.class);
|
||||
}
|
||||
|
||||
private static void assertJdbcCondition(Class<?> type) {
|
||||
ConditionalOnProperty condition = type.getAnnotation(ConditionalOnProperty.class);
|
||||
assertThat(condition).isNotNull();
|
||||
assertThat(condition.name()).containsExactly("ca-skeleton.capabilities.idempotency.provider");
|
||||
assertThat(condition.havingValue()).isEqualTo("jdbc");
|
||||
assertThat(condition.matchIfMissing()).isTrue();
|
||||
}
|
||||
|
||||
private static final class NoOpJdbcStore implements IdempotencyStorePort {
|
||||
|
||||
@Override
|
||||
public boolean tryBegin(
|
||||
IdempotencyScope scope, RequestFingerprint fingerprint, Instant expiresAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<IdempotencyRecord> find(IdempotencyScope scope, Instant now) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(IdempotencyScope scope, StoredResponse response) {}
|
||||
|
||||
@Override
|
||||
public void discard(IdempotencyScope scope) {}
|
||||
}
|
||||
|
||||
private static final class NoOpRedisStore implements IdempotencyStorePortV2 {
|
||||
|
||||
@Override
|
||||
public IdempotencyClaimAttempt newClaimAttempt(String operationId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyClaimOutcome claim(IdempotencyClaimRequest request) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyStartOutcome markExecutionStarted(
|
||||
IdempotencyOwner owner, String operationId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyRenewOutcome renew(
|
||||
IdempotencyOwner owner, Duration processingLeaseTtl, String operationId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyCompleteOutcome complete(
|
||||
IdempotencyOwner owner, StoredResponse response, Duration replayTtl, String operationId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyFailOutcome markFailed(
|
||||
IdempotencyOwner owner,
|
||||
IdempotencyFailureDisposition disposition,
|
||||
Duration retention,
|
||||
String operationId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyReleaseOutcome releaseBeforeExecution(
|
||||
IdempotencyOwner owner, String operationId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdempotencyInspection inspect(IdempotencyInspectionRequest request) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -10,6 +10,13 @@ import dev.caskeleton.adapter.outbound.cache.core.CacheStore;
|
||||
import dev.caskeleton.adapter.outbound.cache.core.CacheStoreRouter;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisCacheAdapterConfig;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisClient;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.RedisRateLimitConfig;
|
||||
import dev.caskeleton.adapter.outbound.fileserver.FileExportConfig;
|
||||
import dev.caskeleton.adapter.outbound.fileserver.FileserverR2Config;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.OutboundHttpClient;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.OutboundHttpShutdownGuard;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.ResolvedHttpClientCapability;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.resilience.OutboundHttpResilience;
|
||||
import dev.caskeleton.adapter.outbound.messaging.MessagingConfig;
|
||||
import dev.caskeleton.adapter.outbound.messaging.core.DisabledMessagePublisher;
|
||||
import dev.caskeleton.adapter.outbound.messaging.core.MessageBroker;
|
||||
@@ -28,12 +35,15 @@ import dev.caskeleton.adapter.outbound.notification.email.google.GoogleEmailNoti
|
||||
import dev.caskeleton.adapter.outbound.notification.slack.webhook.SlackClient;
|
||||
import dev.caskeleton.adapter.outbound.notification.slack.webhook.SlackNotificationAdapterConfig;
|
||||
import dev.caskeleton.adapter.outbound.support.OutboundSupportConfig;
|
||||
import dev.caskeleton.application.filepublication.FilePublicationPort;
|
||||
import dev.caskeleton.application.notification.Channel;
|
||||
import dev.caskeleton.application.notification.Notification;
|
||||
import dev.caskeleton.application.notification.NotificationPort;
|
||||
import dev.caskeleton.application.outbox.OutboxMessagePublishPort;
|
||||
import dev.caskeleton.application.outbox.OutboxRelayFailureReportPort;
|
||||
import dev.caskeleton.bootstrap.httpclient.HttpClientCompositionConfig;
|
||||
import dev.caskeleton.shared.error.AdapterDisabledException;
|
||||
import dev.caskeleton.shared.ratelimit.EdgeRateLimitPort;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
@@ -68,10 +78,14 @@ class OptionalAdapterBeanGatingTest {
|
||||
MessagingConfig.class,
|
||||
KafkaAdapterConfig.class,
|
||||
RedisCacheAdapterConfig.class,
|
||||
RedisRateLimitConfig.class,
|
||||
CacheRouterConfig.class,
|
||||
NotificationConfig.class,
|
||||
SlackNotificationAdapterConfig.class,
|
||||
GoogleEmailNotificationAdapterConfig.class,
|
||||
FileExportConfig.class,
|
||||
FileserverR2Config.class,
|
||||
HttpClientCompositionConfig.class,
|
||||
StubClientsConfig.class);
|
||||
|
||||
@Test
|
||||
@@ -84,7 +98,15 @@ class OptionalAdapterBeanGatingTest {
|
||||
// real provider beans absent (Layer 1 — disabled, contributes nothing)
|
||||
assertThat(context.getBeansOfType(MessageBroker.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(CacheStore.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(EdgeRateLimitPort.class)).isEmpty();
|
||||
assertThat(context.containsBean("distributedRateLimiter")).isFalse();
|
||||
assertThat(context.getBeansOfType(NotificationProvider.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(FilePublicationPort.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OutboundHttpClient.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OutboundHttpShutdownGuard.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OutboundHttpResilience.class)).isEmpty();
|
||||
assertThat(context.getBean(ResolvedHttpClientCapability.class).state())
|
||||
.isEqualTo(ResolvedHttpClientCapability.State.DISABLED_VERIFIED);
|
||||
|
||||
// messaging: fail-fast sentinels satisfy the ports (Layer 3 fallback)
|
||||
assertThat(context.getBean(MessagePublisher.class))
|
||||
@@ -131,12 +153,24 @@ class OptionalAdapterBeanGatingTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyRedisEnableWithoutExplicitMigrationModeCreatesNoBackend() {
|
||||
runner
|
||||
.withPropertyValues("app.cache.redis.enabled=true", "app.cache.redis.client-mode=external")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context.getBeansOfType(CacheBackend.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void redisEnabledContributesTheBackendAndRoutesBoundLogicalCaches() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"app.cache.redis.enabled=true",
|
||||
"app.cache.redis.client-mode=external",
|
||||
"ca-skeleton.providers.redis.legacy-migration-enabled=true",
|
||||
"app.cache.bindings.worklog=redis")
|
||||
.run(
|
||||
context -> {
|
||||
@@ -191,6 +225,7 @@ class OptionalAdapterBeanGatingTest {
|
||||
.withPropertyValues(
|
||||
"app.cache.redis.enabled=true",
|
||||
"app.cache.redis.client-mode=external",
|
||||
"ca-skeleton.providers.redis.legacy-migration-enabled=true",
|
||||
"app.cache.test-second.enabled=true",
|
||||
"app.cache.bindings.worklog=redis",
|
||||
"app.cache.bindings.session=test-second")
|
||||
|
||||
+49
-1
@@ -7,6 +7,7 @@ import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.lang.EvaluationResult;
|
||||
import dev.caskeleton.application.architecture.violations.ApplicationDiagnosticFrameworkViolation;
|
||||
import dev.caskeleton.bootstrap.architecture.allowed.application.CleanProjectionQueryPort;
|
||||
import dev.caskeleton.bootstrap.architecture.fixtures.application.RootWriteTransactionBoundaryUseCase;
|
||||
import dev.caskeleton.bootstrap.architecture.violations.application.BulkWriteWithoutWriteAccessUseCase;
|
||||
import dev.caskeleton.bootstrap.architecture.violations.application.FixtureRepository;
|
||||
import dev.caskeleton.bootstrap.architecture.violations.application.GenericLeakQueryPort;
|
||||
@@ -59,6 +60,8 @@ class ArchitectureViolationFixtureTest {
|
||||
new ClassFileImporter().importClasses(JakartaValidationApplicationFixture.class);
|
||||
private static final JavaClasses APPLICATION_DIAGNOSTIC_FRAMEWORK_FIXTURE_ONLY =
|
||||
new ClassFileImporter().importClasses(ApplicationDiagnosticFrameworkViolation.class);
|
||||
private static final JavaClasses ROOT_WRITE_TRANSACTION_BOUNDARY_FIXTURE_ONLY =
|
||||
new ClassFileImporter().importClasses(RootWriteTransactionBoundaryUseCase.class);
|
||||
|
||||
// Each WebSocket fixture is imported in ISOLATION so the two package globs in
|
||||
// NO_WEBSOCKET_HANDLER ("org.springframework.web.socket.." vs "jakarta.websocket..")
|
||||
@@ -80,6 +83,14 @@ class ArchitectureViolationFixtureTest {
|
||||
new ClassFileImporter().importClasses(RawLeakQueryPort.class, FakeDomainEntity.class);
|
||||
private static final JavaClasses GENERIC_LEAK_QUERY_PORT_ONLY =
|
||||
new ClassFileImporter().importClasses(GenericLeakQueryPort.class, FakeDomainEntity.class);
|
||||
private static final JavaClasses B7_SETTINGS_SUFFIX_BYPASS_ONLY =
|
||||
new ClassFileImporter()
|
||||
.importPackages(
|
||||
"dev.caskeleton.bootstrap.architecture.violations.adapter.outbound.settingsbypass");
|
||||
private static final JavaClasses B7_ACTIVATION_PACKAGE_BYPASS_ONLY =
|
||||
new ClassFileImporter()
|
||||
.importPackages(
|
||||
"dev.caskeleton.bootstrap.architecture.violations.adapter.outbound.httpclient.activation");
|
||||
|
||||
/** Over-block guard corpus: a legitimate projection port the D1 rule must NOT flag. */
|
||||
private static final JavaClasses CLEAN_PROJECTION_QUERY_PORT_ONLY =
|
||||
@@ -261,10 +272,23 @@ class ArchitectureViolationFixtureTest {
|
||||
.as(
|
||||
"USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY must catch "
|
||||
+ "MissingTransactionBoundaryUseCase declaring WRITE_REPOSITORY without "
|
||||
+ "TransactionPort.inWrite")
|
||||
+ "TransactionPort.inWrite or TransactionPort.inRootWrite")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void useCaseCapabilityMatchesTransactionPortBoundaryAllowsRootWriteBoundary() {
|
||||
EvaluationResult result =
|
||||
CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY.evaluate(
|
||||
ROOT_WRITE_TRANSACTION_BOUNDARY_FIXTURE_ONLY);
|
||||
|
||||
assertThat(result.hasViolation())
|
||||
.as(
|
||||
"USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY must allow a "
|
||||
+ "WRITE_REPOSITORY use case that directly calls TransactionPort.inRootWrite")
|
||||
.isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sharedContractScopeRuleCatchesDomainSpecificSharedPackage() {
|
||||
EvaluationResult result =
|
||||
@@ -397,6 +421,30 @@ class ArchitectureViolationFixtureTest {
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAdapterMethodRuleCannotBeBypassedWithSettingsSuffix() {
|
||||
EvaluationResult result =
|
||||
CleanArchitectureTest.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")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundAdapterMethodRuleCannotBeBypassedWithActivationPackage() {
|
||||
EvaluationResult result =
|
||||
CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate(
|
||||
B7_ACTIVATION_PACKAGE_BYPASS_ONLY);
|
||||
|
||||
assertThat(result.hasViolation())
|
||||
.as(
|
||||
"B7 must catch whitelisted-name overloads returning raw adapter types inside an "
|
||||
+ "activation-named package")
|
||||
.isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void controllerRequestMappingsFollowAip122CatchesKebabPathFixture() {
|
||||
EvaluationResult result =
|
||||
|
||||
+261
-36
@@ -387,7 +387,7 @@ class CleanArchitectureTest {
|
||||
"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, REQUIRES_NEW -> inNew. "
|
||||
+ "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.")
|
||||
@@ -583,24 +583,24 @@ class CleanArchitectureTest {
|
||||
String transactionMode = enumAnnotationValue(annotation, "transactionMode");
|
||||
String repositoryAccess = enumAnnotationValue(annotation, "repositoryAccess");
|
||||
|
||||
String requiredMethod = null;
|
||||
Set<String> requiredMethods = Set.of();
|
||||
if ("REQUIRES_NEW".equals(transactionMode)) {
|
||||
requiredMethod = "inNew";
|
||||
requiredMethods = Set.of("inNew");
|
||||
} else if ("WRITE".equals(transactionMode) && "WRITE_REPOSITORY".equals(repositoryAccess)) {
|
||||
requiredMethod = "inWrite";
|
||||
requiredMethods = Set.of("inWrite", "inRootWrite");
|
||||
} else if ("READ_ONLY".equals(transactionMode)
|
||||
&& "READ_REPOSITORY".equals(repositoryAccess)) {
|
||||
requiredMethod = "inRead";
|
||||
requiredMethods = Set.of("inRead");
|
||||
}
|
||||
|
||||
if (requiredMethod == null) {
|
||||
if (requiredMethods.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
|
||||
if ("dev.caskeleton.application.transaction.TransactionPort"
|
||||
.equals(call.getTargetOwner().getFullName())
|
||||
&& requiredMethod.equals(call.getName())) {
|
||||
&& requiredMethods.contains(call.getName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -614,8 +614,8 @@ class CleanArchitectureTest {
|
||||
+ transactionMode
|
||||
+ ", repositoryAccess = "
|
||||
+ repositoryAccess
|
||||
+ ") but does not directly call TransactionPort."
|
||||
+ requiredMethod
|
||||
+ ") but does not directly call one of TransactionPort."
|
||||
+ requiredMethods
|
||||
+ "(...)"));
|
||||
}
|
||||
};
|
||||
@@ -925,9 +925,9 @@ class CleanArchitectureTest {
|
||||
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).")
|
||||
"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) ----
|
||||
@@ -1051,9 +1051,9 @@ class CleanArchitectureTest {
|
||||
.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.")
|
||||
"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 ----
|
||||
@@ -1252,6 +1252,20 @@ class CleanArchitectureTest {
|
||||
.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()
|
||||
@@ -1263,15 +1277,226 @@ class CleanArchitectureTest {
|
||||
"..adapter.inbound.web..",
|
||||
"..adapter.outbound.persistence.."))
|
||||
.as(
|
||||
"B7: outbound 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.")
|
||||
"B7: externally reachable outbound 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<ExactMethodSignature> B7_EXACT_HTTP_CONTROL_PLANE_METHODS =
|
||||
Set.of(
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings",
|
||||
"retry",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings$Retry"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings",
|
||||
"circuitBreaker",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings$CircuitBreaker"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpClientConfig",
|
||||
"outboundHttpShutdownGuard",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpShutdownGuard"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpClientConfig",
|
||||
"outboundHttpErrorMapper",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.diagnostics.OutboundHttpErrorMapper"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpClientConfig",
|
||||
"outboundHttpDependencyLogger",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.diagnostics.OutboundHttpDependencyLogger"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpClientConfig",
|
||||
"outboundRetryPolicy",
|
||||
List.of(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpShutdownGuard",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.diagnostics.OutboundHttpErrorMapper"),
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundRetryPolicy"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.resilience.OutboundHttpResilienceConfig",
|
||||
"outboundHttpResilience",
|
||||
List.of(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.OutboundRetryPolicy",
|
||||
"org.springframework.beans.factory.ObjectProvider"),
|
||||
"dev.caskeleton.adapter.outbound.httpclient.resilience.OutboundHttpResilience"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientActivationResolver",
|
||||
"resolve",
|
||||
List.of(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpOperationCatalogRegistry",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientReadinessCardRegistry"),
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.ResolvedHttpClientCapability"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration",
|
||||
"expectedState",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientExpectedState"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration$DestinationDefinition",
|
||||
"operationCatalogId",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration$OperationCatalogId"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration$DestinationDefinition",
|
||||
"profile",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration$DestinationProfile"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfigurationBinder",
|
||||
"bind",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientReadinessCardRegistry",
|
||||
"require",
|
||||
List.of("java.lang.String"),
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientReadinessCardRegistry$Maturity"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpOperationCatalogRegistry",
|
||||
"require",
|
||||
List.of(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration$OperationCatalogId"),
|
||||
"dev.caskeleton.adapter.outbound.httpclient.operation.HttpOperationCatalog"),
|
||||
signature(
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.ResolvedHttpClientCapability",
|
||||
"state",
|
||||
"dev.caskeleton.adapter.outbound.httpclient.activation.ResolvedHttpClientCapability$State"));
|
||||
|
||||
private static DescribedPredicate<JavaMethod> 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<ExactMethodSignature> 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<JavaMethod> 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<String> parameters, String returnType) {
|
||||
return new ExactMethodSignature(owner, name, parameters, returnType);
|
||||
}
|
||||
|
||||
private record ExactMethodSignature(
|
||||
String owner, String name, List<String> parameters, String returnType) {
|
||||
|
||||
private ExactMethodSignature {
|
||||
parameters = List.copyOf(parameters);
|
||||
}
|
||||
|
||||
private static ExactMethodSignature from(JavaMethod method) {
|
||||
List<String> parameterTypes =
|
||||
method.getRawParameterTypes().stream().map(JavaClass::getFullName).toList();
|
||||
return new ExactMethodSignature(
|
||||
method.getOwner().getFullName(),
|
||||
method.getName(),
|
||||
parameterTypes,
|
||||
method.getRawReturnType().getFullName());
|
||||
}
|
||||
}
|
||||
|
||||
@ArchTest
|
||||
static final ArchRule VALID_CASCADE_DEPTH_AT_MOST_THREE =
|
||||
classes()
|
||||
@@ -1584,11 +1809,11 @@ class CleanArchitectureTest {
|
||||
.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.")
|
||||
"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
|
||||
@@ -1759,15 +1984,15 @@ class CleanArchitectureTest {
|
||||
"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<DomainType> "
|
||||
+ "(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.")
|
||||
"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<DomainType>"
|
||||
+ " (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<JavaMethod> notLeakDomainJpaOrWebThroughReturnType(
|
||||
|
||||
+70
-13
@@ -3,20 +3,26 @@ package dev.caskeleton.bootstrap.architecture;
|
||||
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.JavaClass;
|
||||
import com.tngtech.archunit.core.domain.JavaMethod;
|
||||
import com.tngtech.archunit.junit.AnalyzeClasses;
|
||||
import com.tngtech.archunit.junit.ArchTest;
|
||||
import com.tngtech.archunit.lang.ArchRule;
|
||||
|
||||
/**
|
||||
* feature-integration-adapter-templates Layer 2 (D3 / §구현 가이드 §3) — static isolation + gating guard
|
||||
* for the optional integration adapters (Kafka / Redis / Slack / Google Email). Owner: this branch.
|
||||
* for the optional integration adapters (Kafka / Redis / Slack / Google Email / Fileserver). Owner:
|
||||
* this branch.
|
||||
*
|
||||
* <p>What this layer statically guarantees, and its documented limit (branch-note L111 / D3 Open
|
||||
* Risk): ArchUnit can prove (1) the application layer never imports an optional adapter package,
|
||||
* and (2) every optional-adapter {@code @Bean} is gated by {@code @ConditionalOnProperty} — i.e.
|
||||
* "the adapter candidate class HAS the {@code @ConditionalOnProperty} annotation". Whether the
|
||||
* adapter is actually <em>active</em> at runtime is a config evaluation ArchUnit cannot reach; that
|
||||
* runtime guarantee is delegated to Layer 3 ({@code AdapterDisabledException}).
|
||||
* and (2) every resource-owning optional-adapter {@code @Bean} is gated by
|
||||
* {@code @ConditionalOnProperty}. Redis's exact resource-free observation and zero-binding registry
|
||||
* control-plane beans are exempt: their runtime test proves that an unselected provider resolves no
|
||||
* material and opens no client. Whether a candidate is actually <em>active</em> at runtime is a
|
||||
* config evaluation ArchUnit cannot reach; that runtime guarantee is delegated to Layer 3 ({@code
|
||||
* AdapterDisabledException}).
|
||||
*
|
||||
* <p>Spring annotation types are referenced by fully-qualified NAME so this test needs no compile
|
||||
* dependency on spring-context / spring-boot-autoconfigure (they arrive only via adapter-outbound's
|
||||
@@ -29,7 +35,8 @@ class DisabledAdapterArchitectureTest {
|
||||
"..adapter.outbound.messaging.kafka..",
|
||||
"..adapter.outbound.cache.redis..",
|
||||
"..adapter.outbound.notification.slack..",
|
||||
"..adapter.outbound.notification.email.."
|
||||
"..adapter.outbound.notification.email..",
|
||||
"..adapter.outbound.fileserver.."
|
||||
};
|
||||
|
||||
private static final String BEAN = "org.springframework.context.annotation.Bean";
|
||||
@@ -51,7 +58,8 @@ class DisabledAdapterArchitectureTest {
|
||||
.resideInAnyPackage(OPTIONAL_ADAPTER_PACKAGES)
|
||||
.as(
|
||||
"D3 APPLICATION_DOES_NOT_DEPEND_ON_OPTIONAL_ADAPTERS: the application layer must "
|
||||
+ "not import an optional adapter package (Kafka/Redis/Slack/Google Email) — "
|
||||
+ "not import an optional adapter package "
|
||||
+ "(Kafka/Redis/Slack/Google Email/Fileserver) — "
|
||||
+ "the static half of the disabled-adapter detection. Runtime activity is "
|
||||
+ "delegated to Layer 3 (feature-integration-adapter-templates D3)")
|
||||
.allowEmptyShould(true);
|
||||
@@ -70,14 +78,63 @@ class DisabledAdapterArchitectureTest {
|
||||
.and()
|
||||
.areDeclaredInClassesThat()
|
||||
.resideInAnyPackage(OPTIONAL_ADAPTER_PACKAGES)
|
||||
.and(notCanonicalRedisResourceFreeControlPlaneBeans())
|
||||
.should()
|
||||
.beAnnotatedWith(CONDITIONAL_ON_PROPERTY)
|
||||
.as(
|
||||
"D3 OPTIONAL_ADAPTER_BEANS_ARE_GATED_BY_CONDITIONAL_ON_PROPERTY: every @Bean in an "
|
||||
+ "optional adapter package (Kafka/Redis/Slack/Google Email) must declare "
|
||||
+ "@ConditionalOnProperty(app.<domain>.<adapter>.enabled) — Layer 1 disabled-default "
|
||||
+ "must not be bypassable by an ungated bean. ArchUnit reaches the annotation "
|
||||
+ "presence only; runtime activation is Layer 3's job "
|
||||
+ "(feature-integration-adapter-templates D3, L111)")
|
||||
"D3 OPTIONAL_ADAPTER_BEANS_ARE_GATED_BY_CONDITIONAL_ON_PROPERTY: every @Bean in an"
|
||||
+ " optional adapter package (Kafka/Redis/Slack/Google Email/Fileserver) must"
|
||||
+ " declare @ConditionalOnProperty(app.<domain>.<adapter>.enabled) — Layer 1"
|
||||
+ " disabled-default must not be bypassable by an ungated bean. ArchUnit reaches"
|
||||
+ " the annotation presence only; runtime activation is Layer 3's job"
|
||||
+ " (feature-integration-adapter-templates D3, L111)")
|
||||
.allowEmptyShould(true);
|
||||
|
||||
private static DescribedPredicate<JavaMethod> notCanonicalRedisResourceFreeControlPlaneBeans() {
|
||||
return new DescribedPredicate<>(
|
||||
"not the exact canonical Redis resource-free control-plane beans") {
|
||||
@Override
|
||||
public boolean test(JavaMethod method) {
|
||||
return !isCanonicalRedisObservationPort(method)
|
||||
&& !isCanonicalRedisZeroBindingRegistry(method);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isCanonicalRedisObservationPort(JavaMethod method) {
|
||||
return hasOwnerAndSignature(
|
||||
method,
|
||||
"redisCapabilityObservationPort",
|
||||
"dev.caskeleton.adapter.outbound.cache.redis.RedisCapabilityObservationPort",
|
||||
java.util.List.of("org.springframework.beans.factory.ObjectProvider"));
|
||||
}
|
||||
|
||||
private static boolean isCanonicalRedisZeroBindingRegistry(JavaMethod method) {
|
||||
return hasOwnerAndSignature(
|
||||
method,
|
||||
"redisCanonicalRoleRegistry",
|
||||
"dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalRoleRegistry",
|
||||
java.util.List.of(
|
||||
"dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings",
|
||||
"org.springframework.core.env.Environment",
|
||||
"org.springframework.beans.factory.ObjectProvider",
|
||||
"org.springframework.beans.factory.ObjectProvider",
|
||||
"org.springframework.beans.factory.ObjectProvider",
|
||||
"org.springframework.beans.factory.ObjectProvider",
|
||||
"dev.caskeleton.adapter.outbound.cache.redis.RedisCapabilityObservationPort"));
|
||||
}
|
||||
|
||||
private static boolean hasOwnerAndSignature(
|
||||
JavaMethod method, String name, String returnType, java.util.List<String> parameterTypes) {
|
||||
return method
|
||||
.getOwner()
|
||||
.getFullName()
|
||||
.equals("dev.caskeleton.adapter.outbound.cache.redis.RedisCanonicalConfig")
|
||||
&& method.getName().equals(name)
|
||||
&& method.getRawReturnType().getFullName().equals(returnType)
|
||||
&& method.getRawParameterTypes().stream()
|
||||
.map(JavaClass::getFullName)
|
||||
.toList()
|
||||
.equals(parameterTypes);
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package dev.caskeleton.bootstrap.architecture.fixtures.application;
|
||||
|
||||
import dev.caskeleton.application.capability.Idempotency;
|
||||
import dev.caskeleton.application.capability.RepositoryAccess;
|
||||
import dev.caskeleton.application.capability.UseCaseCapability;
|
||||
import dev.caskeleton.application.command.Command;
|
||||
import dev.caskeleton.application.security.RequiresPermission;
|
||||
import dev.caskeleton.application.transaction.TransactionMode;
|
||||
import dev.caskeleton.application.transaction.TransactionPort;
|
||||
import dev.caskeleton.application.usecase.CommandUseCase;
|
||||
|
||||
/** Positive fixture: a root-only write boundary satisfies the WRITE_REPOSITORY fitness rule. */
|
||||
@UseCaseCapability(
|
||||
transactionMode = TransactionMode.WRITE,
|
||||
idempotency = Idempotency.NOT_IDEMPOTENT,
|
||||
repositoryAccess = RepositoryAccess.WRITE_REPOSITORY)
|
||||
@RequiresPermission("fixture:root-write")
|
||||
public final class RootWriteTransactionBoundaryUseCase
|
||||
implements CommandUseCase<RootWriteTransactionBoundaryUseCase.CommandFixture, String> {
|
||||
|
||||
private final TransactionPort transactionPort;
|
||||
|
||||
public RootWriteTransactionBoundaryUseCase(TransactionPort transactionPort) {
|
||||
this.transactionPort = transactionPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String handle(CommandFixture command) {
|
||||
return transactionPort.inRootWrite(command::value);
|
||||
}
|
||||
|
||||
public record CommandFixture(String value) implements Command {}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package dev.caskeleton.bootstrap.architecture.violations.adapter.outbound.httpclient.activation;
|
||||
|
||||
import dev.caskeleton.bootstrap.architecture.violations.adapter.outbound.RawExternalResponseFixture;
|
||||
|
||||
/** Proves that an activation package name cannot bypass the outbound raw-type leak guard. */
|
||||
public class EvilActivationLeak {
|
||||
|
||||
public RawExternalResponseFixture require() {
|
||||
return new RawExternalResponseFixture();
|
||||
}
|
||||
|
||||
public RawExternalResponseFixture require(String ignored) {
|
||||
return new RawExternalResponseFixture();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package dev.caskeleton.bootstrap.architecture.violations.adapter.outbound.settingsbypass;
|
||||
|
||||
import dev.caskeleton.bootstrap.architecture.violations.adapter.outbound.RawExternalResponseFixture;
|
||||
|
||||
/** Proves that a Settings suffix cannot bypass the outbound raw-type leak guard. */
|
||||
public class EvilSettings {
|
||||
|
||||
public RawExternalResponseFixture leak() {
|
||||
return new RawExternalResponseFixture();
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -8,7 +8,10 @@ import dev.caskeleton.application.security.RequiresPermission;
|
||||
import dev.caskeleton.application.transaction.TransactionMode;
|
||||
import dev.caskeleton.application.usecase.CommandUseCase;
|
||||
|
||||
/** Intentional write-use-case violation: declares a write but skips TransactionPort.inWrite. */
|
||||
/**
|
||||
* Intentional write-use-case violation: declares a write but skips both TransactionPort.inWrite and
|
||||
* TransactionPort.inRootWrite.
|
||||
*/
|
||||
@UseCaseCapability(
|
||||
transactionMode = TransactionMode.WRITE,
|
||||
idempotency = Idempotency.NOT_IDEMPOTENT,
|
||||
|
||||
-14
@@ -5,8 +5,6 @@ import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
|
||||
|
||||
import dev.caskeleton.bootstrap.contract.support.conditional.EnabledIfEmailNotificationConfigured;
|
||||
import dev.caskeleton.bootstrap.contract.support.conditional.EnabledIfHttpCircuitBreakerEnabled;
|
||||
import dev.caskeleton.bootstrap.contract.support.conditional.EnabledIfHttpRetryEnabled;
|
||||
import dev.caskeleton.bootstrap.contract.support.conditional.EnabledIfMessagingBrokerConfigured;
|
||||
import dev.caskeleton.bootstrap.contract.support.conditional.EnabledIfRedisCacheEnabled;
|
||||
import dev.caskeleton.bootstrap.contract.support.conditional.EnabledIfSlackNotificationConfigured;
|
||||
@@ -35,18 +33,6 @@ class OptionalAdapterConditionalExecutionContractTest {
|
||||
assertThat(System.getenv("APP_CACHE_REDIS_ENABLED")).isEqualTo("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfHttpRetryEnabled
|
||||
void httpRetryAdapterRunsOnlyWhenEnabled() {
|
||||
assertThat(System.getenv("APP_OUTBOUND_HTTP_RETRY_ENABLED")).isEqualTo("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfHttpCircuitBreakerEnabled
|
||||
void httpCircuitBreakerAdapterRunsOnlyWhenEnabled() {
|
||||
assertThat(System.getenv("APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_ENABLED")).isEqualTo("true");
|
||||
}
|
||||
|
||||
@Test
|
||||
@EnabledIfMessagingBrokerConfigured
|
||||
void messagingBrokerAdapterRunsOnlyWhenConfigured() {
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
package dev.caskeleton.bootstrap.contract.support.conditional;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
/**
|
||||
* Gates an optional-adapter contract test to the outbound-HTTP-circuit-breaker-enabled env matrix.
|
||||
* Reports DISABLED (= SKIPPED, never FAILED) when {@code APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_ENABLED}
|
||||
* is unset or not {@code true} (feature-contract-verification-test-suite D3).
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@EnabledIfEnvironmentVariable(
|
||||
named = "APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_ENABLED",
|
||||
matches = "true",
|
||||
disabledReason =
|
||||
"Outbound HTTP circuit breaker disabled "
|
||||
+ "(APP_OUTBOUND_HTTP_CIRCUIT_BREAKER_ENABLED != true) — optional-adapter contract "
|
||||
+ "test runs only in the circuit-breaker-enabled env matrix")
|
||||
public @interface EnabledIfHttpCircuitBreakerEnabled {}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
package dev.caskeleton.bootstrap.contract.support.conditional;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.junit.jupiter.api.condition.EnabledIfEnvironmentVariable;
|
||||
|
||||
/**
|
||||
* Gates an optional-adapter contract test to the outbound-HTTP-retry-enabled env matrix. Reports
|
||||
* DISABLED (= SKIPPED, never FAILED) when {@code APP_OUTBOUND_HTTP_RETRY_ENABLED} is unset or not
|
||||
* {@code true} (feature-contract-verification-test-suite D3).
|
||||
*/
|
||||
@Target({ElementType.TYPE, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@EnabledIfEnvironmentVariable(
|
||||
named = "APP_OUTBOUND_HTTP_RETRY_ENABLED",
|
||||
matches = "true",
|
||||
disabledReason =
|
||||
"Outbound HTTP retry disabled (APP_OUTBOUND_HTTP_RETRY_ENABLED != true) — "
|
||||
+ "optional-adapter contract test runs only in the retry-enabled env matrix")
|
||||
public @interface EnabledIfHttpRetryEnabled {}
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
package dev.caskeleton.bootstrap.httpclient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.httpclient.OutboundHttpClient;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.OutboundHttpSettings;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.OutboundHttpShutdownGuard;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpClientCanonicalConfiguration;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.HttpOperationCatalogRegistry;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.activation.ResolvedHttpClientCapability;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.operation.HttpDestinationId;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.operation.HttpOperationCatalog;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.operation.HttpOperationDescriptor;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.operation.HttpOperationId;
|
||||
import dev.caskeleton.adapter.outbound.httpclient.resilience.OutboundHttpResilience;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.web.client.RestClient;
|
||||
|
||||
class HttpClientCompositionConfigTest {
|
||||
|
||||
private final ApplicationContextRunner runner =
|
||||
new ApplicationContextRunner().withUserConfiguration(HttpClientCompositionConfig.class);
|
||||
|
||||
private final ApplicationContextRunner applicationYamlRunner =
|
||||
new ApplicationContextRunner()
|
||||
.withInitializer(HttpClientCompositionConfigTest::loadApplicationYaml)
|
||||
.withUserConfiguration(HttpClientCompositionConfig.class);
|
||||
|
||||
@Test
|
||||
void defaultZeroBindingPublishesOnlyAnInertDisabledDescriptorAndNoRuntimeResources() {
|
||||
runner.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context.getBean(ResolvedHttpClientCapability.class).state())
|
||||
.isEqualTo(ResolvedHttpClientCapability.State.DISABLED_VERIFIED);
|
||||
assertThat(context.getBeansOfType(OutboundHttpClient.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OutboundHttpSettings.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(RestClient.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(RestClient.Builder.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OutboundHttpShutdownGuard.class)).isEmpty();
|
||||
assertThat(context.getBeansOfType(OutboundHttpResilience.class)).isEmpty();
|
||||
assertThat(beanNamesFor(context, "io.github.resilience4j.retry.RetryRegistry")).isEmpty();
|
||||
assertThat(
|
||||
beanNamesFor(
|
||||
context, "io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry"))
|
||||
.isEmpty();
|
||||
assertThat(context.containsBean("outboundCallExecutor")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledWithABindingFailsStartup() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.http-client.expected-state=DISABLED",
|
||||
"ca-skeleton.capabilities.http-client.bindings.partner=jdk-r1")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getMessage()).contains("DISABLED");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void activeFailsOnNotImplementedReadinessBeforeRuntimeResourcesExist() {
|
||||
runner
|
||||
.withBean(
|
||||
HttpOperationCatalogRegistry.class, HttpClientCompositionConfigTest::activeCatalog)
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.http-client.expected-state=ACTIVE",
|
||||
"ca-skeleton.capabilities.http-client.bindings.partner=jdk-r1",
|
||||
"ca-skeleton.providers.http-client.jdk-r1.destinations.partner.operation-catalog=partner-v1",
|
||||
"ca-skeleton.providers.http-client.jdk-r1.destinations.partner.profile=BUFFERED_CLASSIC")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getMessage())
|
||||
.contains("httpclient-static-buffered")
|
||||
.contains("NOT_IMPLEMENTED");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void activeCanonicalSelectionRejectsLegacySpringInputBeforeResolution() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.http-client.expected-state=ACTIVE",
|
||||
"ca-skeleton.capabilities.http-client.bindings.partner=jdk-r1",
|
||||
"app.outbound.http.connect-timeout=2s")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getMessage())
|
||||
.contains("canonical")
|
||||
.contains("legacy");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void actualApplicationYamlKeepsLegacyInputAbsentAndZeroBindingDisabled() {
|
||||
applicationYamlRunner.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context.getEnvironment().getProperty("app.outbound.http.connect-timeout"))
|
||||
.isNull();
|
||||
assertThat(context.getBean(ResolvedHttpClientCapability.class).state())
|
||||
.isEqualTo(ResolvedHttpClientCapability.State.DISABLED_VERIFIED);
|
||||
assertThat(context.getBeansOfType(OutboundHttpClient.class)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void actualApplicationYamlActiveFailsAtReadinessRatherThanLegacyConflict() {
|
||||
applicationYamlRunner
|
||||
.withBean(
|
||||
HttpOperationCatalogRegistry.class, HttpClientCompositionConfigTest::activeCatalog)
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.http-client.expected-state=ACTIVE",
|
||||
"ca-skeleton.capabilities.http-client.bindings.partner=jdk-r1",
|
||||
"ca-skeleton.providers.http-client.jdk-r1.destinations.partner.operation-catalog=partner-v1",
|
||||
"ca-skeleton.providers.http-client.jdk-r1.destinations.partner.profile=BUFFERED_CLASSIC")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure().getMessage())
|
||||
.contains("httpclient-static-buffered")
|
||||
.contains("NOT_IMPLEMENTED")
|
||||
.doesNotContain("legacy");
|
||||
});
|
||||
}
|
||||
|
||||
private static HttpOperationCatalogRegistry activeCatalog() {
|
||||
HttpDestinationId destination = new HttpDestinationId("partner");
|
||||
HttpOperationDescriptor operation =
|
||||
new HttpOperationDescriptor(
|
||||
new HttpOperationId("partner.fetch.v1"),
|
||||
destination,
|
||||
1,
|
||||
HttpOperationDescriptor.Method.GET,
|
||||
"/items/{id}",
|
||||
HttpOperationDescriptor.OperationSemantics.SAFE_READ,
|
||||
HttpOperationDescriptor.RequestMode.NONE,
|
||||
HttpOperationDescriptor.ResponseMode.BUFFERED,
|
||||
Set.of(200),
|
||||
0,
|
||||
1,
|
||||
1024);
|
||||
return new HttpOperationCatalogRegistry(
|
||||
Map.of(
|
||||
new HttpClientCanonicalConfiguration.OperationCatalogId("partner-v1"),
|
||||
new HttpOperationCatalog(List.of(operation))));
|
||||
}
|
||||
|
||||
private static String[] beanNamesFor(ApplicationContext context, String className) {
|
||||
try {
|
||||
return context.getBeanNamesForType(Class.forName(className));
|
||||
} catch (ClassNotFoundException exception) {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadApplicationYaml(ConfigurableApplicationContext context) {
|
||||
try {
|
||||
new YamlPropertySourceLoader()
|
||||
.load("application.yml", new ClassPathResource("application.yml"))
|
||||
.forEach(context.getEnvironment().getPropertySources()::addLast);
|
||||
} catch (IOException exception) {
|
||||
throw new UncheckedIOException(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package dev.caskeleton.bootstrap.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial;
|
||||
import dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisTrustMaterial;
|
||||
import dev.caskeleton.bootstrap.runtime.SecretSource;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
|
||||
class RedisEnvironmentMaterialProviderTest {
|
||||
|
||||
@Test
|
||||
void resolvesOnlyAllowlistedEnvironmentReferencesAndDescribesRestartOnlyRotation() {
|
||||
AtomicInteger resolutions = new AtomicInteger();
|
||||
SecretSource source =
|
||||
key -> {
|
||||
resolutions.incrementAndGet();
|
||||
return Optional.of(
|
||||
key.endsWith("TRUST_PEM")
|
||||
? "-----BEGIN CERTIFICATE-----\ninvalid-test-body\n-----END CERTIFICATE-----"
|
||||
: "private-material");
|
||||
};
|
||||
RedisEnvironmentCredentialMaterialProvider credentialProvider =
|
||||
new RedisEnvironmentCredentialMaterialProvider(source);
|
||||
RedisEnvironmentTrustMaterialProvider trustProvider =
|
||||
new RedisEnvironmentTrustMaterialProvider(source);
|
||||
|
||||
try (VersionedRedisCredentialMaterial credential =
|
||||
credentialProvider.resolve(
|
||||
RedisSecretReference.parse("secret://environment/APP_RATE_LIMIT_REDIS_PASSWORD"));
|
||||
VersionedRedisTrustMaterial trust =
|
||||
trustProvider.resolve(
|
||||
RedisSecretReference.parse(
|
||||
"secret://environment/APP_RATE_LIMIT_REDIS_TRUST_PEM"))) {
|
||||
String credentialValue = credential.useSecret(String::new);
|
||||
int trustBytes = trust.usePem(bytes -> bytes.length);
|
||||
assertThat(credentialValue).isEqualTo("private-material");
|
||||
assertThat(trustBytes).isPositive();
|
||||
}
|
||||
|
||||
assertThat(resolutions).hasValue(2);
|
||||
assertThat(credentialProvider.descriptor().changeEventsSupported()).isFalse();
|
||||
assertThat(trustProvider.descriptor().refreshMode())
|
||||
.isEqualTo("restart-or-explicit-runtime-recomposition");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownSchemeKeyBlankOversizeAndProviderFailuresAreSanitized() {
|
||||
RedisEnvironmentCredentialMaterialProvider blank =
|
||||
new RedisEnvironmentCredentialMaterialProvider(ignored -> Optional.of(" "));
|
||||
RedisEnvironmentCredentialMaterialProvider oversized =
|
||||
new RedisEnvironmentCredentialMaterialProvider(ignored -> Optional.of("x".repeat(16_385)));
|
||||
RedisEnvironmentCredentialMaterialProvider leaking =
|
||||
new RedisEnvironmentCredentialMaterialProvider(
|
||||
ignored -> {
|
||||
throw new IllegalStateException("raw-secret-and-reference");
|
||||
});
|
||||
|
||||
for (org.assertj.core.api.ThrowableAssert.ThrowingCallable call :
|
||||
java.util.List.<org.assertj.core.api.ThrowableAssert.ThrowingCallable>of(
|
||||
() ->
|
||||
blank.resolve(
|
||||
RedisSecretReference.parse("secret://environment/APP_CACHE_REDIS_PASSWORD")),
|
||||
() ->
|
||||
oversized.resolve(
|
||||
RedisSecretReference.parse("secret://environment/APP_CACHE_REDIS_PASSWORD")),
|
||||
() ->
|
||||
leaking.resolve(
|
||||
RedisSecretReference.parse("secret://environment/APP_CACHE_REDIS_PASSWORD")),
|
||||
() ->
|
||||
blank.resolve(
|
||||
RedisSecretReference.parse("secret://vault/APP_CACHE_REDIS_PASSWORD")),
|
||||
() ->
|
||||
blank.resolve(
|
||||
RedisSecretReference.parse("secret://environment/UNREGISTERED_SECRET")))) {
|
||||
assertThatThrownBy(call)
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessage("Canonical Redis environment material resolution failed")
|
||||
.hasMessageNotContaining("raw-secret")
|
||||
.hasMessageNotContaining("APP_CACHE")
|
||||
.hasNoCause();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesEveryCanonicalHmacReferenceShippedInApplicationYaml() throws Exception {
|
||||
var properties =
|
||||
new YamlPropertySourceLoader()
|
||||
.load("application.yml", new ClassPathResource("application.yml"))
|
||||
.getFirst();
|
||||
RedisEnvironmentCredentialMaterialProvider provider =
|
||||
new RedisEnvironmentCredentialMaterialProvider(
|
||||
ignored -> Optional.of("cHJvZHVjdGlvbi1zYWZlLWhhcmRlbmVkLXRlc3QtaG1hYy1tYXRlcmlhbA=="));
|
||||
|
||||
for (String property :
|
||||
java.util.List.of(
|
||||
"ca-skeleton.capabilities.cache.regions.default.key-hmac-secret-reference",
|
||||
"ca-skeleton.capabilities.rate-limit.key-hmac-secret-reference",
|
||||
"ca-skeleton.capabilities.idempotency.key-hmac-secret-reference",
|
||||
"ca-skeleton.capabilities.lease.key-hmac-secret-reference",
|
||||
"ca-skeleton.capabilities.security.redis-session.key-hmac-secret-reference")) {
|
||||
String reference = String.valueOf(properties.getProperty(property));
|
||||
try (VersionedRedisCredentialMaterial material =
|
||||
provider.resolve(RedisSecretReference.parse(reference))) {
|
||||
String resolved = material.useSecret(String::new);
|
||||
assertThat(resolved).isNotBlank();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
-7
@@ -2,12 +2,14 @@ package dev.caskeleton.bootstrap.runtime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration;
|
||||
import org.springframework.boot.env.YamlPropertySourceLoader;
|
||||
import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroups;
|
||||
import org.springframework.boot.health.actuate.endpoint.StatusAggregator;
|
||||
import org.springframework.boot.health.autoconfigure.actuate.endpoint.HealthEndpointAutoConfiguration;
|
||||
@@ -20,6 +22,9 @@ import org.springframework.boot.health.contributor.Status;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
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;
|
||||
|
||||
/**
|
||||
* TDD contract test for feature-runtime-health-lifecycle-contract.
|
||||
@@ -67,14 +72,33 @@ class RuntimeHealthLifecycleContractTest {
|
||||
"management.health.readinessstate.enabled=true",
|
||||
// The three production group properties under test.
|
||||
"management.endpoint.health.probes.enabled=true",
|
||||
"management.endpoint.health.validate-group-membership=false",
|
||||
"management.endpoint.health.group.liveness.include=livenessState",
|
||||
"management.endpoint.health.group.readiness.include=readinessState,db",
|
||||
"management.endpoint.health.group.readiness.include=readinessState,db,redisRequired",
|
||||
"management.endpoint.health.group.startup.include=readinessState");
|
||||
|
||||
// =========================================================================
|
||||
// 1. Three health groups are configured
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
@DisplayName(
|
||||
"shipped health groups keep Redis out of liveness and optional Redis out of readiness")
|
||||
void shippedHealthGroupSettingsPreserveRedisDependencyTaxonomy() throws IOException {
|
||||
MutablePropertySources sources = new MutablePropertySources();
|
||||
new YamlPropertySourceLoader()
|
||||
.load("application", new ClassPathResource("application.yml"))
|
||||
.forEach(sources::addLast);
|
||||
PropertySourcesPropertyResolver properties = new PropertySourcesPropertyResolver(sources);
|
||||
|
||||
assertThat(properties.getProperty("management.endpoint.health.group.liveness.include"))
|
||||
.isEqualTo("livenessState");
|
||||
assertThat(properties.getProperty("management.endpoint.health.group.readiness.include"))
|
||||
.isEqualTo("readinessState,db,redisRequired");
|
||||
assertThat(properties.getProperty("management.endpoint.health.validate-group-membership"))
|
||||
.isEqualTo("false");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("health probes: liveness group is configured")
|
||||
void livenessGroupIsConfigured() {
|
||||
@@ -100,7 +124,7 @@ class RuntimeHealthLifecycleContractTest {
|
||||
assertThat(groups.get("readiness"))
|
||||
.as(
|
||||
"readiness group must be configured "
|
||||
+ "(management.endpoint.health.group.readiness.include=readinessState,db)")
|
||||
+ "(include=readinessState,db,redisRequired)")
|
||||
.isNotNull();
|
||||
});
|
||||
}
|
||||
@@ -140,7 +164,7 @@ class RuntimeHealthLifecycleContractTest {
|
||||
HealthEndpointGroups groups = ctx.getBean(HealthEndpointGroups.class);
|
||||
var readiness = groups.get("readiness");
|
||||
assertThat(readiness).as("readiness group must exist").isNotNull();
|
||||
// The group membership is defined by "include=readinessState,db".
|
||||
// The group membership is defined by "include=readinessState,db,redisRequired".
|
||||
// isMember() returns true when the contributor name is in the include list.
|
||||
assertThat(readiness.isMember("db"))
|
||||
.as(
|
||||
@@ -150,6 +174,19 @@ class RuntimeHealthLifecycleContractTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("readiness includes required Redis but excludes optional cache Redis")
|
||||
void readinessIncludesOnlyTheRequiredRedisContributor() {
|
||||
runner.run(
|
||||
ctx -> {
|
||||
assertThat(ctx).hasNotFailed();
|
||||
var readiness = ctx.getBean(HealthEndpointGroups.class).get("readiness");
|
||||
assertThat(readiness).as("readiness group must exist").isNotNull();
|
||||
assertThat(readiness.isMember("redisRequired")).isTrue();
|
||||
assertThat(readiness.isMember("redisOptional")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("liveness group does NOT include db (liveness is independent of REQUIRED deps)")
|
||||
void livenessGroupDoesNotIncludeDb() {
|
||||
@@ -164,6 +201,8 @@ class RuntimeHealthLifecycleContractTest {
|
||||
"liveness group must NOT include 'db' "
|
||||
+ "(a DOWN DB must not flip liveness — the JVM can still continue)")
|
||||
.isFalse();
|
||||
assertThat(liveness.isMember("redisRequired")).isFalse();
|
||||
assertThat(liveness.isMember("redisOptional")).isFalse();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,9 +251,10 @@ class RuntimeHealthLifecycleContractTest {
|
||||
void jvmDefaultTimezoneIsUtc() {
|
||||
assertThat(TimeZone.getDefault().getID())
|
||||
.as(
|
||||
"JVM default timezone must be UTC — enforced by -Duser.timezone=UTC in the "
|
||||
+ "app-bootstrap test task. A drift here means the test JVM arg was removed. "
|
||||
+ "Production UTC is owned by feature-container-runtime-contract (TZ=UTC in Dockerfile).")
|
||||
"JVM default timezone must be UTC — enforced by -Duser.timezone=UTC in the"
|
||||
+ " app-bootstrap test task. A drift here means the test JVM arg was removed."
|
||||
+ " Production UTC is owned by feature-container-runtime-contract (TZ=UTC in"
|
||||
+ " Dockerfile).")
|
||||
.isEqualTo("UTC");
|
||||
}
|
||||
|
||||
@@ -223,7 +263,7 @@ class RuntimeHealthLifecycleContractTest {
|
||||
// Mirrors the liveness/readiness isMember tests (sections 1 and 2).
|
||||
// The startup group is configured with include=readinessState — assert
|
||||
// membership explicitly to give the startup group the same coverage parity
|
||||
// as liveness (livenessState) and readiness (readinessState,db).
|
||||
// as liveness (livenessState) and readiness (readinessState,db,redisRequired).
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
|
||||
+99
@@ -3,6 +3,7 @@ package dev.caskeleton.bootstrap.runtime;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.bootstrap.runtime.startup.StartupValidationException;
|
||||
import java.util.Arrays;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -30,6 +31,8 @@ class SecretSourceValidatorTest {
|
||||
"APP_EXTERNAL_API_KEY=real-api-key",
|
||||
"APP_CACHE_REDIS_PASSWORD=real-redis-password",
|
||||
"APP_CACHE_REDIS_KEY_HMAC_SECRET=real-redis-key-hmac-secret",
|
||||
"APP_RATE_LIMIT_REDIS_PASSWORD=real-rate-limit-redis-password",
|
||||
"APP_RATE_LIMIT_REDIS_KEY_HMAC_SECRET=real-rate-limit-redis-key-hmac-secret",
|
||||
"APP_PRIVACY_PSEUDONYMIZATION_SALT=real-salt"
|
||||
};
|
||||
}
|
||||
@@ -106,6 +109,102 @@ class SecretSourceValidatorTest {
|
||||
.run(context -> assertThat(context).hasNotFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void enabledRateLimitRedisRequiresItsDedicatedSecretsInProd() {
|
||||
runner
|
||||
.withInitializer(ctx -> ctx.getEnvironment().setActiveProfiles("prod"))
|
||||
.withPropertyValues(allRequiredSecretsPresent())
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.rate-limit.provider=redis",
|
||||
"APP_RATE_LIMIT_REDIS_KEY_HMAC_SECRET=")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.isInstanceOf(StartupValidationException.class)
|
||||
.hasStackTraceContaining("APP_RATE_LIMIT_REDIS_KEY_HMAC_SECRET");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void disabledRateLimitRedisDoesNotRequireItsDedicatedSecretsInProd() {
|
||||
String[] baselineSecrets =
|
||||
Arrays.stream(allRequiredSecretsPresent())
|
||||
.filter(value -> !value.startsWith("APP_RATE_LIMIT_REDIS_"))
|
||||
.toArray(String[]::new);
|
||||
|
||||
runner
|
||||
.withInitializer(ctx -> ctx.getEnvironment().setActiveProfiles("prod"))
|
||||
.withPropertyValues(baselineSecrets)
|
||||
.withPropertyValues("ca-skeleton.capabilities.rate-limit.provider=disabled")
|
||||
.run(context -> assertThat(context).hasNotFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void transportEnableAloneDoesNotRequireRedisProviderSecretsInProd() {
|
||||
String[] baselineSecrets =
|
||||
Arrays.stream(allRequiredSecretsPresent())
|
||||
.filter(value -> !value.startsWith("APP_RATE_LIMIT_REDIS_"))
|
||||
.toArray(String[]::new);
|
||||
|
||||
runner
|
||||
.withInitializer(ctx -> ctx.getEnvironment().setActiveProfiles("prod"))
|
||||
.withPropertyValues(baselineSecrets)
|
||||
.withPropertyValues("app.rate-limit.enabled=true")
|
||||
.run(context -> assertThat(context).hasNotFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectedRedisIdempotencyRequiresItsHmacMaterialInProd() {
|
||||
runner
|
||||
.withInitializer(ctx -> ctx.getEnvironment().setActiveProfiles("prod"))
|
||||
.withPropertyValues(allRequiredSecretsPresent())
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.idempotency.provider=redis",
|
||||
"APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET=")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.isInstanceOf(StartupValidationException.class)
|
||||
.hasStackTraceContaining("APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectedRedisEfficiencyLeaseRequiresItsHmacMaterialInProd() {
|
||||
runner
|
||||
.withInitializer(ctx -> ctx.getEnvironment().setActiveProfiles("prod"))
|
||||
.withPropertyValues(allRequiredSecretsPresent())
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.lease.provider=redis", "APP_LEASE_REDIS_KEY_HMAC_SECRET=")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.isInstanceOf(StartupValidationException.class)
|
||||
.hasStackTraceContaining("APP_LEASE_REDIS_KEY_HMAC_SECRET");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void redisSessionModeRequiresItsHmacMaterialInProd() {
|
||||
runner
|
||||
.withInitializer(ctx -> ctx.getEnvironment().setActiveProfiles("prod"))
|
||||
.withPropertyValues(allRequiredSecretsPresent())
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.security.auth-mode=redis-session",
|
||||
"APP_SESSION_REDIS_PASSWORD=real-session-password",
|
||||
"APP_SESSION_REDIS_KEY_HMAC_SECRET=")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.isInstanceOf(StartupValidationException.class)
|
||||
.hasStackTraceContaining("APP_SESSION_REDIS_KEY_HMAC_SECRET");
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ValidatorConfig {
|
||||
@Bean
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package dev.caskeleton.bootstrap.runtime.redis;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Capability;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.EvictionAttestation;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.EvictionPolicy;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Reason;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Role;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.RoleHealth;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.Snapshot;
|
||||
import dev.caskeleton.shared.health.RedisHealthSnapshotProvider.State;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.health.contributor.HealthIndicator;
|
||||
import org.springframework.boot.health.contributor.Status;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
class RedisHealthContributorConfigTest {
|
||||
|
||||
private static final Instant OBSERVED_AT = Instant.parse("2026-07-29T01:02:03Z");
|
||||
|
||||
private final ApplicationContextRunner runner =
|
||||
new ApplicationContextRunner().withUserConfiguration(RedisHealthContributorConfig.class);
|
||||
|
||||
@Test
|
||||
void noRoleBindingCreatesNoRedisHealthContributor() {
|
||||
runner
|
||||
.withBean(RedisHealthSnapshotProvider.class, () -> () -> snapshot())
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).doesNotHaveBean("redisRequired");
|
||||
assertThat(context).doesNotHaveBean("redisOptional");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void declaredButUnselectedRoleCreatesNoRedisHealthContributor() {
|
||||
runner
|
||||
.withPropertyValues("ca-skeleton.providers.redis.roles.cache.deployment-id=cache-main")
|
||||
.withBean(RedisHealthSnapshotProvider.class, () -> () -> snapshot())
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).doesNotHaveBean("redisRequired");
|
||||
assertThat(context).doesNotHaveBean("redisOptional");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void optionalCacheOutageStaysUpAndReportsOnlyDegradedSanitizedDetail() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.cache.bindings.default=redis",
|
||||
"ca-skeleton.providers.redis.roles.cache.deployment-id=cache-main")
|
||||
.withBean(
|
||||
RedisHealthSnapshotProvider.class,
|
||||
() ->
|
||||
() ->
|
||||
snapshot(
|
||||
role(
|
||||
Role.CACHE,
|
||||
false,
|
||||
Set.of(Capability.CACHE),
|
||||
State.UNAVAILABLE,
|
||||
Reason.SEMANTIC_PROGRAM_FAILED,
|
||||
EvictionPolicy.ALLKEYS_LFU)))
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasBean("redisOptional");
|
||||
assertThat(context).doesNotHaveBean("redisRequired");
|
||||
|
||||
var health = context.getBean("redisOptional", HealthIndicator.class).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails())
|
||||
.containsEntry("state", "DEGRADED")
|
||||
.doesNotContainKey("exception");
|
||||
assertThat(health.toString())
|
||||
.contains("SEMANTIC_PROGRAM_FAILED", "INCOMPLETE")
|
||||
.doesNotContain("cache-main");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void requiredCoordinationOutageTurnsRequiredContributorDown() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.capabilities.rate-limit.provider=redis",
|
||||
"ca-skeleton.providers.redis.roles.coordination.deployment-id=coord-main")
|
||||
.withBean(
|
||||
RedisHealthSnapshotProvider.class,
|
||||
() ->
|
||||
() ->
|
||||
snapshot(
|
||||
role(
|
||||
Role.COORDINATION,
|
||||
true,
|
||||
Set.of(Capability.RATE_LIMIT, Capability.IDEMPOTENCY),
|
||||
State.UNAVAILABLE,
|
||||
Reason.SEMANTIC_PROGRAM_ACL_DENIED,
|
||||
EvictionPolicy.NOEVICTION)))
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
assertThat(context).hasBean("redisRequired");
|
||||
assertThat(context).doesNotHaveBean("redisOptional");
|
||||
|
||||
var health = context.getBean("redisRequired", HealthIndicator.class).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
|
||||
assertThat(health.getDetails())
|
||||
.containsEntry("state", "UNAVAILABLE")
|
||||
.containsEntry("missingRoles", List.of());
|
||||
assertThat(health.toString()).contains("SEMANTIC_PROGRAM_ACL_DENIED");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingRequiredSessionSnapshotFailsClosed() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.security.auth-mode=redis-session",
|
||||
"ca-skeleton.providers.redis.roles.session.deployment-id=session-main")
|
||||
.withBean(RedisHealthSnapshotProvider.class, () -> () -> snapshot())
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
var health = context.getBean("redisRequired", HealthIndicator.class).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
|
||||
assertThat(health.getDetails())
|
||||
.containsEntry("missingRoles", List.of(Role.SESSION.name()));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void availableRequiredRoleIsUpWhileEvictionRemainsExplicitlyConfigOnly() {
|
||||
runner
|
||||
.withPropertyValues(
|
||||
"ca-skeleton.security.auth-mode=redis-session",
|
||||
"ca-skeleton.providers.redis.roles.session.deployment-id=session-main")
|
||||
.withBean(
|
||||
RedisHealthSnapshotProvider.class,
|
||||
() ->
|
||||
() ->
|
||||
snapshot(
|
||||
role(
|
||||
Role.SESSION,
|
||||
true,
|
||||
Set.of(Capability.SESSION),
|
||||
State.AVAILABLE,
|
||||
Reason.SEMANTIC_PROBE_SUCCEEDED,
|
||||
EvictionPolicy.NOEVICTION)))
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasNotFailed();
|
||||
var health = context.getBean("redisRequired", HealthIndicator.class).health();
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.toString())
|
||||
.contains(EvictionAttestation.CONFIGURED_EXPECTATION_ONLY.name(), "INCOMPLETE");
|
||||
});
|
||||
}
|
||||
|
||||
private static Snapshot snapshot(RoleHealth... roles) {
|
||||
return new Snapshot(OBSERVED_AT, List.of(roles));
|
||||
}
|
||||
|
||||
private static RoleHealth role(
|
||||
Role role,
|
||||
boolean required,
|
||||
Set<Capability> capabilities,
|
||||
State state,
|
||||
Reason reason,
|
||||
EvictionPolicy eviction) {
|
||||
return new RoleHealth(
|
||||
role,
|
||||
"deployment-id-never-exposed",
|
||||
required,
|
||||
eviction,
|
||||
EvictionAttestation.CONFIGURED_EXPECTATION_ONLY,
|
||||
capabilities,
|
||||
state,
|
||||
reason,
|
||||
OBSERVED_AT,
|
||||
0,
|
||||
false);
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package dev.caskeleton.bootstrap.security;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
|
||||
class AuthenticationModeCompositionConfigTest {
|
||||
|
||||
private final ApplicationContextRunner runner =
|
||||
new ApplicationContextRunner()
|
||||
.withUserConfiguration(AuthenticationModeCompositionConfig.class);
|
||||
|
||||
@Test
|
||||
void jwtModeRequiresOnlyJwtInfrastructure() {
|
||||
runner
|
||||
.withBean("jwtDecoder", Object.class, Object::new)
|
||||
.withPropertyValues("ca-skeleton.security.auth-mode=jwt")
|
||||
.run(context -> assertThat(context).hasNotFailed());
|
||||
|
||||
runner
|
||||
.withBean("jwtDecoder", Object.class, Object::new)
|
||||
.withBean("redisVersionedSessionRepository", Object.class, Object::new)
|
||||
.withPropertyValues("ca-skeleton.security.auth-mode=jwt")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.hasMessage(
|
||||
"Authentication mode composition is not exclusive for JWT: [Redis Session repository/filter is active]");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void redisSessionModeRequiresCompleteSessionInfrastructureAndNoJwtDecoder() {
|
||||
runner
|
||||
.withBean("redisVersionedSessionRepository", Object.class, Object::new)
|
||||
.withBean("springSessionRepositoryFilter", Object.class, Object::new)
|
||||
.withPropertyValues("ca-skeleton.security.auth-mode=redis-session")
|
||||
.run(context -> assertThat(context).hasNotFailed());
|
||||
|
||||
runner
|
||||
.withBean("jwtDecoder", Object.class, Object::new)
|
||||
.withBean("redisVersionedSessionRepository", Object.class, Object::new)
|
||||
.withPropertyValues("ca-skeleton.security.auth-mode=redis-session")
|
||||
.run(
|
||||
context -> {
|
||||
assertThat(context).hasFailed();
|
||||
assertThat(context.getStartupFailure())
|
||||
.hasMessage(
|
||||
"Authentication mode composition is not exclusive for REDIS_SESSION: [jwtDecoder is active, Redis Session repository/filter is incomplete]");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -150,15 +150,3 @@ ca-skeleton:
|
||||
sampling-rate: 1.0
|
||||
privacy:
|
||||
pseudonymization-salt: __LOCAL_DEV_test_salt
|
||||
|
||||
# feature-outbound-http-client-baseline: required timeout properties for any test
|
||||
# context that scans dev.caskeleton (OutboundHttpSettings requires non-zero timeouts).
|
||||
app:
|
||||
outbound:
|
||||
http:
|
||||
connect-timeout: 2s
|
||||
read-timeout: 5s
|
||||
global-call-timeout: 10s
|
||||
retry-enabled: false
|
||||
circuit-breaker-enabled: false
|
||||
response-size-limit: 10MB
|
||||
|
||||
Reference in New Issue
Block a user