From 1535481794ea38817eb62edf9681575361705175 Mon Sep 17 00:00:00 2001 From: DongHyeonka Date: Wed, 16 Sep 2026 16:53:25 +0900 Subject: [PATCH] =?UTF-8?q?refactor(build,src):=20testkit=20=EC=86=8C?= =?UTF-8?q?=EC=8A=A4=EC=85=8B=20=EC=9D=B4=EA=B4=80=EA=B3=BC=20=EB=B9=8C?= =?UTF-8?q?=EB=93=9C=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=20=EC=A0=95=EC=83=81?= =?UTF-8?q?=ED=99=94,=20=EB=AF=B8=EC=B6=94=EC=A0=81=20=EB=B9=8C=EB=93=9C?= =?UTF-8?q?=20=ED=8C=8C=EC=9D=BC=20=EC=B6=94=EC=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 한 커밋인 이유: src/build.gradle 안에서 ca.testkit-publisher 플러그인 제거와 게이트 수정이 얽혀 있다. 플러그인 적용부만 빼면 web·websocket·persistence-jpa· persistence-mongo·app-bootstrap 이 사라진 testkitPublisher() 와 *Testkit 컨피규레이션을 계속 참조해 설정 단계에서 빌드가 죽는다. 파일 단위로 나눌 수 없다. 1) testkit 소스셋 → Gradle 표준 java-test-fixtures 이관 web, websocket, persistence-jpa, persistence-mongo, httpclient, graphql 과 이들의 testkit 컨피규레이션을 소비하던 app-bootstrap. 자체 제작 ca.testkit-publisher.gradle 77줄이 사라진다. 2) 실행되지 않거나 실패할 수 없던 빌드 게이트 정상화 (E등급) - strict-test-lane 의 실행 카운터가 skip 을 실행으로 세던 것 수정. 전부 skip 인 레인은 이제 실패한다 (회귀 테스트 2건 추가) - public-path 스냅샷이 gitignore 된 src/.env 를 읽던 것을 config/security.yml 의 바인딩 기본값으로 교체 - verifyEnvKeys 가 build/ 산출물을 소스로 읽어 삭제된 키를 사용 중으로 오판하던 것 수정 (입력 4,637 → 4,630 파일) - jpa-evidence 가 git 실패를 "워크트리 깨끗함"으로 읽던 것을 fail-closed 로 - notification-evidence 의 Grade 열 탐지를 헤더 기준으로 교체 + 표 부재 시 fail-closed - spring70CompatibilityTest 가 레인을 복제하며 잃은 fail-closed 복구 (태스크명 유지 — 워크플로 3곳과 gate-matrix 린트 무손상) - 메시징 R2 스켈레톤 주변의 도달 불가 검증 45줄을 MSG-015 명시적 실패로 교체 3) git 에 없던 빌드 필수 파일 추적 - src/gradle/libs.versions.toml — src/build.gradle 이 9곳에서 참조하는데 추적되지 않아 깨끗한 체크아웃에서 설정이 실패했다 - app-bootstrap config/*.yml 15개 — application.yml 이 전부 import 한다. 하드코딩된 시크릿은 없고 값은 secret://environment/APP_* 참조다 4) 진행 중이던 구현 작업 반영 (redis/idempotency 구성, startup 검증, 아키텍처 테스트 클래스, notification 콜백 레지스트리 등) 검증: - 깨끗한 체크아웃에서 ./gradlew help 통과 - :app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*' → 20개 클래스 174 tests, 실패 0, 스킵 0 (이전에는 0개 실행) 미해결: verifyOneTypePerFile 은 손대지 않았다(Checkstyle 로 교체 권고). B/C/D 등급 100여 건과 CI 단계 분리는 별도 작업 — docs/superpowers/plans/2026-09-16-ci-stage-separation.md 참고. Co-Authored-By: Claude Opus 5 (1M context) --- src/adapter/inbound/web/build.gradle | 50 +- src/adapter/inbound/web/gradle.lockfile | 222 +- .../ForbiddenClassAnnotationCondition.java | 0 .../ForbiddenMethodAnnotationCondition.java | 0 .../testkit/arch/WebArchitectureRules.java | 0 .../testkit/arch/WebForbiddenTypeCatalog.java | 0 .../budget/BudgetFixtureController.java | 0 .../testkit/budget/BudgetFixtureProtocol.java | 0 .../web/testkit/budget/HttpBudgetFixture.java | 0 .../ReactiveBudgetFixtureController.java | 0 .../web/testkit/budget/WebBudgetContract.java | 0 .../testkit/contract/WebContractFixture.java | 0 .../WebPlatformContractRecording.java | 0 .../contract/WebPlatformContractSuite.java | 0 .../web/testkit/contract/WireOutcome.java | 0 .../web/testkit/contract/WireProbe.java | 0 .../testkit/fault/FaultFixtureController.java | 0 .../testkit/fault/FaultFixtureProtocol.java | 0 .../web/testkit/fault/FaultFixtureStore.java | 0 .../fault/HttpResponseLossFixture.java | 0 .../IdempotencyResponseLossContract.java | 0 .../fault/ReactiveFaultFixtureController.java | 0 .../testkit/fault/ResponseLossFixture.java | 0 .../web/testkit/fault/WebFaultInjector.java | 0 .../web/testkit/fault/WebFaultPoint.java | 0 .../mvc/ContractFixtureController.java | 0 .../testkit/mvc/WebContractAssertions.java | 0 .../operation/HttpOperationFixture.java | 0 .../operation/OperationFixtureController.java | 0 .../operation/OperationFixtureSupport.java | 0 .../operation/OperationHttpContract.java | 0 .../ReactiveOperationFixtureController.java | 0 .../operation/TestDurableOperationStore.java | 0 .../testkit/order/HttpPipelineFixture.java | 0 .../order/PipelineOrderFixtureController.java | 0 .../order/WebPipelineOrderContract.java | 0 .../testkit/order/WebPipelineRecorder.java | 0 .../web/testkit/order/WebPipelineStage.java | 0 .../performance/GracefulShutdownProbe.java | 0 .../WebLoadAndShutdownContract.java | 0 .../testkit/performance/WebLoadFixture.java | 0 .../testkit/performance/WebLoadResult.java | 0 .../testkit/proxy/ProxyFixtureController.java | 0 .../testkit/throttle/CountingRateLimiter.java | 0 .../testkit/throttle/HttpThrottleFixture.java | 0 .../throttle/ThrottleControlController.java | 0 .../throttle/ThrottleFixtureController.java | 0 .../throttle/ThrottleFixtureProtocol.java | 0 .../throttle/ThrottleFixtureSupport.java | 0 .../throttle/WebThrottleHttpContract.java | 0 .../ReactiveContractFixtureController.java | 0 .../webtestkit/BudgetFixtureApplication.java | 0 .../ContractFixtureApplication.java | 0 .../PipelineOrderFixtureApplication.java | 0 .../ThrottleFixtureApplication.java | 0 .../performance/web-load-profile.yaml | 0 src/adapter/inbound/websocket/build.gradle | 33 +- src/adapter/inbound/websocket/gradle.lockfile | 174 +- .../codec/BinaryCodecBackendScopeTest.java | 58 + .../arch/WebSocketArchitectureRules.java | 0 .../testkit/fault/WebSocketFaultInjector.java | 0 .../testkit/fault/WebSocketFaultPoint.java | 0 .../testkit/runtime/SlowConsumerContract.java | 0 .../runtime/WebSocketAbuseContract.java | 0 .../runtime/WebSocketFixtureApplication.java | 0 .../runtime/WebSocketRuntimeContract.java | 0 src/adapter/outbound/fileserver/build.gradle | 6 + .../outbound/fileserver/gradle.lockfile | 3 + .../MicrometerFileserverMetrics.java | 2 +- src/adapter/outbound/httpclient/build.gradle | 87 +- .../outbound/httpclient/gradle.lockfile | 285 +- .../architecture/PlatformClasses.java | 21 +- .../testkit/BlockingTransportContract.java | 0 .../httpclient/testkit/ClientProfiles.java | 0 .../httpclient/testkit/CoreFixtures.java | 0 .../DynamicTargetSecurityContract.java | 0 .../httpclient/testkit/DynamicTargets.java | 0 .../httpclient/testkit/EventPayload.java | 0 .../testkit/Http2FailureFixture.java | 0 .../testkit/HttpClientContract.java | 0 .../httpclient/testkit/InvalidPostClient.java | 0 .../httpclient/testkit/MissingKeyClient.java | 0 .../testkit/MixedSignatureClient.java | 0 .../httpclient/testkit/MockHttpServer.java | 0 .../testkit/NettyLeakDetectionExtension.java | 0 .../testkit/NoopLifecycleListener.java | 0 .../httpclient/testkit/OAuth2Fixture.java | 0 .../testkit/ObservabilityContract.java | 0 .../httpclient/testkit/ProxyFixture.java | 0 .../testkit/ReactiveEventsClient.java | 0 .../testkit/ReactiveTestGateways.java | 0 .../testkit/ReactiveTransportContract.java | 0 .../testkit/RecordedHttpRequest.java | 0 .../testkit/RecordingCredentialProvider.java | 0 .../RecordingResilienceComponents.java | 0 .../httpclient/testkit/RecordingSleeper.java | 0 .../testkit/ResourceLifecycleContract.java | 0 .../httpclient/testkit/RetryContexts.java | 0 .../testkit/RetrySafetyContract.java | 0 .../httpclient/testkit/StatefulUpstream.java | 0 .../httpclient/testkit/TestGateways.java | 0 .../httpclient/testkit/TlsFixture.java | 0 .../httpclient/testkit/TlsMaterials.java | 0 .../httpclient/testkit/ToxiproxyFixture.java | 0 .../httpclient/testkit/UnsafeRetryClient.java | 0 .../httpclient/testkit/UserResponse.java | 0 .../httpclient/testkit/UsersClient.java | 0 src/adapter/outbound/identifier/build.gradle | 2 +- src/adapter/outbound/messaging/build.gradle | 8 +- .../outbound/notification/build.gradle | 9 +- .../outbound/notification/gradle.lockfile | 3 + .../MapProviderCallbackAdapterRegistry.java | 62 + .../MapProviderEventProjectorRegistry.java | 37 + .../MicrometerNotificationMetrics.java | 2 +- .../outbound/objectstorage/build.gradle | 4 +- .../config/ObjectStorageBindingCompiler.java | 14 +- .../outbound/persistence-jpa/build.gradle | 50 +- .../outbound/persistence-jpa/gradle.lockfile | 251 +- .../persistence/JpaModuleBoundaryTest.java | 15 +- .../testkit/arch/EntityExposureCondition.java | 0 .../testkit/arch/EntityMappingCondition.java | 0 .../testkit/arch/JpaArchitectureRules.java | 0 .../testkit/arch/JpaAuditMechanismRule.java | 0 .../arch/TenantScopedRepositoryPredicate.java | 0 .../testkit/envers/AuditedDocument.java | 0 .../testkit/failure/CommitAmbiguityProxy.java | 0 .../failure/PostgreSqlFailureScenario.java | 0 .../fetch/FetchPaginationExpectation.java | 0 .../persistence/testkit/fetch/PagedChild.java | 0 .../testkit/fetch/PagedParent.java | 0 .../testkit/id/IdentityEntity.java | 0 .../testkit/id/SequenceEntity.java | 0 .../testkit/id/UuidV7Generator.java | 0 .../testkit/jdbc/CountingDataSource.java | 0 .../jdbc/CountingJdbcBatchCounter.java | 0 .../testkit/lifecycle/EntityState.java | 0 .../testkit/lifecycle/EntityStateProbe.java | 0 .../testkit/lifecycle/LifecycleChild.java | 0 .../testkit/lifecycle/LifecycleParent.java | 0 .../mapping/DurationMillisConverter.java | 0 .../testkit/mapping/MappingEntity.java | 0 .../persistence/testkit/mapping/Money.java | 0 .../migration/MigrationContractRunner.java | 0 .../testkit/migration/MigrationScenario.java | 0 .../testkit/migration/MigrationSnapshot.java | 0 .../testkit/pool/PoolMeasurement.java | 0 .../PostgreSqlContainerFactory.java | 0 .../PostgreSqlContractExtension.java | 0 .../testkit/postgresql/PostgreSqlVersion.java | 0 .../testkit/query/FetchExpectation.java | 0 .../testkit/query/JpaQueryAssertions.java | 0 .../testkit/query/QueryExpectation.java | 0 .../testkit/query/QueryMeasurement.java | 0 .../testkit/queryplan/NormalizedPlan.java | 0 .../queryplan/PostgreSqlExplainRunner.java | 0 .../queryplan/QueryPlanAssertions.java | 0 .../queryplan/QueryPlanExpectation.java | 0 .../testkit/release/JpaReleaseGate.java | 0 .../testkit/release/JpaReleaseManifest.java | 0 .../testkit/release/JpaReleaseRendering.java | 0 .../outbound/persistence-mongo/build.gradle | 61 +- .../persistence-mongo/gradle.lockfile | 183 +- .../architecture/MongoCollectionProfile.java | 0 .../mongo/architecture/MongoOperation.java | 0 .../MongoRepositoryArchitectureRules.java | 0 .../mongo/testkit/arch/MongoAccessRules.java | 0 .../testkit/arch/MongoAdvancedRules.java | 0 .../MongoAtlasCapabilityContractSuite.java | 0 .../atlas/MongoAtlasLocalContainer.java | 0 .../compat/MongoStableContractSuite.java | 0 .../compat/MongoVersionCapabilityReport.java | 0 .../testkit/compat/MongoVersionMatrix.java | 0 .../failover/MongoFailoverScenario.java | 0 .../failover/MongoNetworkFaultController.java | 0 .../failover/MongoPrimaryController.java | 0 .../failover/MongoProxiedReplicaSetNode.java | 0 .../failover/MongoThreeNodeReplicaSet.java | 0 .../ToxiproxyMongoNetworkFaultController.java | 0 .../testkit/mapping/MongoBsonSnapshot.java | 0 .../mapping/MongoBsonSnapshotAssert.java | 0 .../mapping/MongoRoundTripContract.java | 0 .../MongoBackfillRestartFixture.java | 0 .../MongoMigrationContractSuite.java | 0 .../MongoMigrationSnapshotFixture.java | 0 .../testkit/performance/MongoChaosGate.java | 0 .../performance/MongoPerformanceGate.java | 0 .../MongoResourceBudgetReport.java | 0 .../testkit/release/MongoReleaseContract.java | 0 .../testkit/release/MongoReleaseEvidence.java | 0 .../release/MongoReleaseEvidenceVerifier.java | 0 ...MongoAuthenticatedReplicaSetContainer.java | 0 .../testkit/rs/MongoReplicaSetContract.java | 0 .../testkit/rs/MongoReplicaSetFixture.java | 0 .../rs/MongoSingleReplicaSetContainer.java | 0 .../rs/MongoTlsReplicaSetContainer.java | 0 .../MongoChunkMigrationController.java | 0 .../sharded/MongoShardingContractSuite.java | 0 src/app-bootstrap/CLAUDE.md | 51 + src/app-bootstrap/build.gradle | 50 +- src/app-bootstrap/gradle.lockfile | 687 ++-- .../DistributedLockProviderContractTest.java | 4 +- ...wayMigrationCompatibilityContractTest.java | 0 .../IdempotencyUniqueScopeContractTest.java | 0 .../integration/PostgreSqlTestContainer.java | 0 .../FileserverRoundTripContractTest.java | 0 ...OutboxAppendTransactionalContractTest.java | 0 .../outbox/OutboxContainerTestSupport.java | 0 ...oxPublisherLeaderElectionContractTest.java | 0 .../OutboxRowLifecycleContractTest.java | 0 .../integration/outbox/package-info.java | 0 .../bootstrap/integration/package-info.java | 0 .../bootstrap/CaSkeletonApplication.java | 8 +- .../activation/AdapterActivationEndpoint.java | 2 +- ...abilityDependencyEnvironmentValidator.java | 10 +- .../CapabilityDependencyStartupCheck.java | 3 +- .../MasterSwitchEnvironmentPostProcessor.java | 10 +- .../RuntimeEnvironmentProfileValidator.java | 38 +- .../bootstrap/async/BackgroundJobMetrics.java | 7 +- .../autoconfigure/StrictSettingsBinder.java | 83 + .../FileserverPlatformAutoConfiguration.java | 4 +- .../FileserverPlatformSettings.java | 27 +- .../FileserverPlatformSettingsBinder.java | 35 +- .../FileserverSecurityConfiguration.java | 22 +- .../FileserverStartupConfiguration.java | 13 +- .../FileserverStartupValidator.java | 54 +- .../FileserverStorageConfiguration.java | 7 +- ...ClientAuthenticationAutoConfiguration.java | 3 +- .../httpclient/HttpClientEnvironmentKeys.java | 3 +- .../HttpClientPlatformSettings.java | 9 +- .../HttpClientPlatformSettingsBinder.java | 51 +- .../HttpClientProfileAutoConfiguration.java | 22 +- .../idempotency/IdempotencyComposition.java | 39 + .../idempotency/IdempotencyConfig.java | 2 +- .../IdempotencyProviderSelectionConfig.java | 36 +- .../IdempotencyProviderSettings.java | 67 + .../idempotency/IdempotencySettings.java | 2 +- .../PostgreSqlIdempotencyProviderConfig.java | 2 +- .../DataSourceRequirement.java | 2 +- .../DatabaseReadinessGroupPostProcessor.java | 34 +- .../jpa/JpaDangerousConfigurationGuard.java | 3 +- .../jpa/JpaDataSourceProfileValidator.java | 3 +- .../JpaOffAutoConfigurationImportFilter.java | 2 +- .../PersistenceJpaRootAutoConfiguration.java | 5 +- .../jpa/PostgreSqlVersionPolicy.java | 3 +- .../lock/DistributedLockConfig.java | 2 +- .../lock/MeteredDistributedLockPort.java | 12 +- .../mongo/MongoPlatformHealthConfig.java | 2 +- .../mongo/MongoPlatformHealthContributor.java | 2 +- .../NotificationPlatformCodecConfig.java | 2 +- .../NotificationPlatformDispatchConfig.java | 2 +- ...tificationPlatformObservabilityConfig.java | 8 +- .../NotificationPlatformPolicyConfig.java | 2 +- .../NotificationPlatformProviderConfig.java | 2 +- .../NotificationPlatformRegistriesConfig.java | 30 +- .../NotificationPlatformRuntimeConfig.java | 2 +- .../NotificationPlatformSecretsConfig.java | 21 +- .../NotificationPlatformWorkerConfig.java | 2 +- .../NotificationRootAutoConfiguration.java | 2 +- .../NotificationHealthEndpoint.java | 2 +- .../NotificationObservationContext.java | 2 +- .../NotificationObservationConvention.java | 2 +- .../outbox/OutboxConfig.java | 2 +- .../outbox/OutboxLeaderElectionToken.java | 2 +- .../outbox/OutboxMetrics.java | 18 +- ...OutboxRelayBrokerRequirementValidator.java | 5 +- .../outbox/OutboxRelayScheduler.java | 2 +- .../outbox/OutboxSettings.java | 2 +- .../redis}/RedisActivationValidator.java | 32 +- .../autoconfigure/redis/RedisCacheConfig.java | 60 + .../redis/RedisCapabilityConfig.java | 68 + .../redis/RedisCapabilitySettings.java | 11 +- .../redis/RedisIdempotencyConfig.java | 79 + .../autoconfigure/redis/RedisKeys.java | 103 + .../autoconfigure/redis/RedisLeaseConfig.java | 45 + .../redis/RedisRateLimitConfig.java | 133 + .../RedisReadinessGroupPostProcessor.java | 33 +- .../tracing/MicrometerSpanErrorRecorder.java | 2 +- .../tracing/TracingConfig.java | 2 +- .../tracing/TracingSampleRateResolver.java | 2 +- ...acingSamplingEnvironmentPostProcessor.java | 14 +- .../tracing/TracingSamplingRateGauge.java | 9 +- .../tracing/TracingSettings.java | 2 +- .../IdempotencyProviderSettings.java | 30 - .../redis/RedisCapabilityConfig.java | 376 --- .../HikariPoolConstraintValidator.java | 23 +- .../runtime/JpaSchemaSafetyValidator.java | 10 +- .../PersistenceVendorProdSafetyValidator.java | 10 +- .../PostgreSqlTransportSecurityValidator.java | 9 +- .../bootstrap/runtime/ReadinessGroup.java | 66 + .../RuntimeNumericBoundsValidator.java | 18 +- .../bootstrap/runtime/RuntimeWarnings.java | 85 + .../bootstrap/runtime/SecretSourceConfig.java | 1 + .../runtime/SecretSourceValidator.java | 11 +- .../runtime/StartupSafetyValidator.java | 52 +- .../startup/FlywayProdSafetyValidator.java | 34 +- .../startup/RequiredEnvironmentValidator.java | 16 +- .../runtime/startup/StartupFailures.java | 15 +- .../runtime/startup/StartupOrder.java | 66 + .../startup/StartupValidationContext.java | 173 + .../startup/StartupValidationException.java | 17 +- .../AuthenticationModeCompositionConfig.java | 32 +- .../security/AuthenticationModeSettings.java | 53 +- .../bootstrap/settings/LoggingSettings.java | 37 +- .../bootstrap/settings/PrivacySettings.java | 13 +- .../main/resources/META-INF/spring.factories | 8 +- ...ot.autoconfigure.AutoConfiguration.imports | 4 +- .../src/main/resources/application.yml | 774 +---- .../main/resources/config/capabilities.yml | 71 + .../src/main/resources/config/fileserver.yml | 127 + .../src/main/resources/config/graphql.yml | 12 + .../src/main/resources/config/httpclient.yml | 7 + .../src/main/resources/config/idempotency.yml | 10 + .../src/main/resources/config/messaging.yml | 16 + .../main/resources/config/notification.yml | 145 + .../main/resources/config/observability.yml | 175 ++ .../src/main/resources/config/outbox.yml | 30 + .../src/main/resources/config/persistence.yml | 41 + .../src/main/resources/config/ratelimit.yml | 17 + .../src/main/resources/config/redis.yml | 15 + .../src/main/resources/config/runtime.yml | 65 + .../src/main/resources/config/security.yml | 63 + .../src/main/resources/config/server.yml | 28 + .../AdapterActivationInventory.java | 2 +- .../MasterSwitchRegistryContractTest.java | 5 +- .../NotificationRootImportCoverageTest.java | 12 +- .../AdapterBoundaryArchitectureTest.java | 620 ++++ .../ApplicationBoundaryArchitectureTest.java | 841 +++++ .../ArchRuleDiscoveryContractTest.java | 235 ++ .../ArchitectureViolationFixtureTest.java | 148 +- .../CleanArchitectureRuleSupport.java | 70 + .../architecture/CleanArchitectureTest.java | 2792 ----------------- ...mpositionRootBoundaryArchitectureTest.java | 83 + .../DomainFeatureOnboardingContractTest.java | 27 +- .../DomainPurityArchitectureTest.java | 250 ++ .../ForbiddenApiArchitectureTest.java | 663 ++++ ...tartupFailureContractArchitectureTest.java | 139 + .../TestTaxonomyArchitectureTest.java | 126 +- .../TypeBoundaryArchitectureTest.java | 532 ++++ .../architecture/violations/package-info.java | 4 +- .../IdempotencyProviderCompositionTest.java | 2 +- .../IdempotencyProviderScanCollisionTest.java | 2 +- .../idempotency/IdempotencySettingsTest.java | 2 +- .../CapabilityEntityScanRegistrationTest.java | 2 +- .../lock/MeteredDistributedLockPortTest.java | 2 +- .../MongoPlatformHealthContributorTest.java | 2 +- .../NotificationAutoConfigurationTest.java | 2 +- ...ficationLegacyNamespaceRetirementTest.java | 2 +- .../NotificationModeSsotTest.java | 2 +- ...NotificationPayloadAtRestContractTest.java | 2 +- .../NotificationPlatformReachabilityTest.java | 2 +- .../NotificationSecretsValidationTest.java | 4 +- .../NotificationWorkerLifecycleTest.java | 2 +- .../NotificationObservationTest.java | 3 +- .../outbox/OutboxConfigTest.java | 2 +- ...oxRelayBrokerRequirementValidatorTest.java | 2 +- .../outbox/OutboxSettingsTest.java | 2 +- .../redis}/RedisActivationValidatorTest.java | 2 +- .../redis/RedisCapabilityCompositionTest.java | 2 +- .../RedisReadinessGroupPostProcessorTest.java | 14 +- .../MicrometerSpanErrorRecorderTest.java | 2 +- .../tracing/TracingActivationContextTest.java | 2 +- .../TracingSampleRateResolverTest.java | 2 +- ...gSamplingEnvironmentPostProcessorTest.java | 2 +- .../tracing/TracingSettingsTest.java | 2 +- .../ConfigurationNamespaceContractTest.java | 192 ++ .../DistributedTracingContractTest.java | 6 +- ...anagementActuatorSecurityContractTest.java | 24 +- .../ProfileSeparationContractTest.java | 12 +- .../MessagingPublicSurfaceContractTest.java | 42 +- .../support/ShippedConfiguration.java | 159 + .../RuntimeHealthLifecycleContractTest.java | 7 +- ...thenticationModeCompositionConfigTest.java | 9 +- .../PublicHealthPathAgreementTest.java | 9 + .../settings/LoggingSettingsTest.java | 6 +- .../settings/PrivacySettingsTest.java | 8 +- src/application-core/build.gradle | 2 +- src/build-logic/build.gradle | 4 +- src/build-logic/settings.gradle | 14 + .../main/groovy/ca.strict-test-lane.gradle | 32 +- .../main/groovy/ca.testkit-publisher.gradle | 77 - .../StrictTestLaneConventionTest.groovy | 94 + src/build.gradle | 431 ++- src/config/spotbugs/exclude.xml | 2 +- src/gradle/jpa-evidence.gradle | 33 +- src/gradle/libs.versions.toml | 86 + src/gradle/notification-evidence.gradle | 63 +- src/gradle/public-path-snapshot.gradle | 79 +- .../messaging-cloudevents/build.gradle | 4 +- src/messaging/messaging-kafka/build.gradle | 2 +- .../messaging-nats-experimental/build.gradle | 2 +- .../build.gradle | 2 +- .../messaging-schema-avro/build.gradle | 2 +- .../messaging-schema-protobuf/build.gradle | 2 +- src/sample-portfolio/build.gradle | 8 +- src/settings-gradle.lockfile | 4 + .../shared/runtime/RuntimeEnvironment.java | 177 ++ .../runtime/RuntimeEnvironmentTest.java | 97 + 397 files changed, 8912 insertions(+), 5916 deletions(-) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java (100%) rename src/adapter/inbound/web/src/{testkit => testFixtures}/resources/performance/web-load-profile.yaml (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java (100%) rename src/adapter/inbound/websocket/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java (100%) rename src/{app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver => adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability}/MicrometerFileserverMetrics.java (98%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java (100%) rename src/adapter/outbound/httpclient/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java (100%) create mode 100644 src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java create mode 100644 src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java rename src/{app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification => adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform}/observation/MicrometerNotificationMetrics.java (97%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java (100%) rename src/adapter/outbound/persistence-jpa/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java (100%) rename src/adapter/outbound/persistence-mongo/src/{testkit => testFixtures}/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java (99%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java (100%) rename src/app-bootstrap/src/{test => integrationTest}/java/dev/caskeleton/bootstrap/integration/package-info.java (100%) create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/IdempotencyConfig.java (94%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/IdempotencyProviderSelectionConfig.java (74%) create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/IdempotencySettings.java (95%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/PostgreSqlIdempotencyProviderConfig.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/{persistencejpa => jpa}/DataSourceRequirement.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{runtime => autoconfigure/jpa}/DatabaseReadinessGroupPostProcessor.java (61%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/{persistencejpa => jpa}/JpaOffAutoConfigurationImportFilter.java (97%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/{persistencejpa => jpa}/PersistenceJpaRootAutoConfiguration.java (95%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/lock/DistributedLockConfig.java (96%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/lock/MeteredDistributedLockPort.java (91%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/mongo/MongoPlatformHealthConfig.java (96%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/mongo/MongoPlatformHealthContributor.java (97%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformCodecConfig.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformDispatchConfig.java (99%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformObservabilityConfig.java (93%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformPolicyConfig.java (99%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformProviderConfig.java (99%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformRegistriesConfig.java (73%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformRuntimeConfig.java (97%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformSecretsConfig.java (96%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformWorkerConfig.java (99%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationRootAutoConfiguration.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/observation/NotificationHealthEndpoint.java (97%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/observation/NotificationObservationContext.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/observation/NotificationObservationConvention.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxConfig.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxLeaderElectionToken.java (92%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxMetrics.java (89%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxRelayBrokerRequirementValidator.java (93%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxRelayScheduler.java (97%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxSettings.java (97%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{runtime => autoconfigure/redis}/RedisActivationValidator.java (73%) create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/redis/RedisCapabilitySettings.java (97%) create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{runtime => autoconfigure/redis}/RedisReadinessGroupPostProcessor.java (69%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/MicrometerSpanErrorRecorder.java (92%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingConfig.java (98%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSampleRateResolver.java (96%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSamplingEnvironmentPostProcessor.java (72%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSamplingRateGauge.java (81%) rename src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSettings.java (98%) delete mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java delete mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java create mode 100644 src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java create mode 100644 src/app-bootstrap/src/main/resources/config/capabilities.yml create mode 100644 src/app-bootstrap/src/main/resources/config/fileserver.yml create mode 100644 src/app-bootstrap/src/main/resources/config/graphql.yml create mode 100644 src/app-bootstrap/src/main/resources/config/httpclient.yml create mode 100644 src/app-bootstrap/src/main/resources/config/idempotency.yml create mode 100644 src/app-bootstrap/src/main/resources/config/messaging.yml create mode 100644 src/app-bootstrap/src/main/resources/config/notification.yml create mode 100644 src/app-bootstrap/src/main/resources/config/observability.yml create mode 100644 src/app-bootstrap/src/main/resources/config/outbox.yml create mode 100644 src/app-bootstrap/src/main/resources/config/persistence.yml create mode 100644 src/app-bootstrap/src/main/resources/config/ratelimit.yml create mode 100644 src/app-bootstrap/src/main/resources/config/redis.yml create mode 100644 src/app-bootstrap/src/main/resources/config/runtime.yml create mode 100644 src/app-bootstrap/src/main/resources/config/security.yml create mode 100644 src/app-bootstrap/src/main/resources/config/server.yml create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java delete mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/IdempotencyProviderCompositionTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/IdempotencyProviderScanCollisionTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/idempotency/IdempotencySettingsTest.java (95%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/{persistencejpa => jpa}/CapabilityEntityScanRegistrationTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/lock/MeteredDistributedLockPortTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/mongo/MongoPlatformHealthContributorTest.java (95%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationAutoConfigurationTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationLegacyNamespaceRetirementTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationModeSsotTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPayloadAtRestContractTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationPlatformReachabilityTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationSecretsValidationTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/NotificationWorkerLifecycleTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/notification/observation/NotificationObservationTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxConfigTest.java (87%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxRelayBrokerRequirementValidatorTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/outbox/OutboxSettingsTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{runtime => autoconfigure/redis}/RedisActivationValidatorTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/redis/RedisCapabilityCompositionTest.java (99%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{runtime => autoconfigure/redis}/RedisReadinessGroupPostProcessorTest.java (95%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/MicrometerSpanErrorRecorderTest.java (96%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingActivationContextTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSampleRateResolverTest.java (98%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSamplingEnvironmentPostProcessorTest.java (96%) rename src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/{ => autoconfigure}/tracing/TracingSettingsTest.java (99%) create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java create mode 100644 src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/support/ShippedConfiguration.java delete mode 100644 src/build-logic/src/main/groovy/ca.testkit-publisher.gradle create mode 100644 src/gradle/libs.versions.toml create mode 100644 src/settings-gradle.lockfile create mode 100644 src/shared-contract/src/main/java/dev/caskeleton/shared/runtime/RuntimeEnvironment.java create mode 100644 src/shared-contract/src/test/java/dev/caskeleton/shared/runtime/RuntimeEnvironmentTest.java diff --git a/src/adapter/inbound/web/build.gradle b/src/adapter/inbound/web/build.gradle index ee75f0c5..ea9fb27f 100644 --- a/src/adapter/inbound/web/build.gradle +++ b/src/adapter/inbound/web/build.gradle @@ -1,3 +1,6 @@ +// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. +apply plugin: 'java-test-fixtures' + // The inbound HTTP API execution platform design models itself as 23 Stable Gradle modules under // modules/web. This repository's fail-closed module registry outranks that layout, so those modules // are packages here and WebModuleBoundaryTest enforces the design's module dependency table. The @@ -14,14 +17,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.session:spring-session-core' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' - implementation('org.openapitools:jackson-databind-nullable:0.2.6') { + implementation(libs.jackson.databind.nullable) { exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind' } // feature-api-contract-baseline D10: OpenAPI producer. springdoc exposes the // running app's machine-readable contract at /v3/api-docs (OAS 3.1, generated — // never a hand-maintained stale schema). The release-blocking drift gate is // owned by feature-contract-verification-test-suite (planned). - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0' + implementation libs.springdoc.openapi.starter.webmvc.api // The two Advanced representations, compile-only on purpose. They were `implementation` first, // on the reasoning that a missing backend would surface as a NoClassDefFoundError at the first // request that negotiated one. That reasoning was wrong about what the jars do: Spring Boot's @@ -65,13 +68,12 @@ tasks.named('test') { // tests and by the composition root. A rule pack that only its own fixture tests import is verified // as library code and applied to nothing — the shape the JPA testkit had to be corrected out of. strictTestLanes { - sourceSet('testkit') { compilesAgainst 'main' } // The Jetty compatibility lane is its own source set because it needs a different embedded // server on the classpath. Two servers in one source set means Spring Boot picks one and the // "Jetty" lane silently runs on Tomcat — a compatibility matrix that certifies the same // container twice. sourceSet('jettyCompatTest') { - compilesAgainst 'main', 'testkit' + compilesAgainst 'main', 'testFixtures' inherits 'implementation' } // Reactor Netty is the Stable WebFlux server baseline and it cannot share a source set with @@ -85,11 +87,11 @@ strictTestLanes { // developer's `check` depend on a container runtime, and the usual outcome of that is an // @Disabled that nobody notices has been there for months. sourceSet('nginxProxyTest') { - compilesAgainst 'main', 'testkit' + compilesAgainst 'main', 'testFixtures' inherits 'implementation' } sourceSet('webfluxContractTest') { - compilesAgainst 'main', 'testkit' + compilesAgainst 'main', 'testFixtures' // Inherits nothing. The default is to extend `testImplementation`, which extends the leaf's // own `implementation` and therefore carries spring-boot-starter-web — and with Tomcat on // the classpath Boot deduces a servlet application, starts a servlet container, and the @@ -98,16 +100,36 @@ strictTestLanes { } } -testkitPublisher { - consumedBy 'test' - publishAs 'webTestkit' -} - -// ArchUnit is declared after the testkit source set exists, because `testkitImplementation` is +// ArchUnit is declared after the testkit source set exists, because `testFixturesImplementation` is // created by that declaration. dependencies { - testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' + // The fixtures speak the application's ports, so they compile against it directly. Under the + // old source set this arrived through testImplementation inheritance. + testFixturesImplementation project(':application-core') + testFixturesImplementation project(':shared-contract') + + // Declared rather than inherited: the `testkit` source set extended testImplementation, so the + // fixtures compiled against every test library this leaf declared without stating any of them + // (ADR-BUILD-001). Listing them makes the fixtures' own surface reviewable. + testFixturesImplementation 'jakarta.servlet:jakarta.servlet-api' + testFixturesImplementation 'jakarta.validation:jakarta.validation-api' + testFixturesImplementation 'org.assertj:assertj-core' + testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api' + testFixturesImplementation 'org.springframework:spring-web' + testFixturesImplementation 'org.springframework:spring-webmvc' + testFixturesImplementation 'org.springframework:spring-context' + testFixturesImplementation 'org.springframework:spring-core' + testFixturesImplementation 'org.springframework.boot:spring-boot' + testFixturesImplementation 'org.springframework.boot:spring-boot-test' + testFixturesImplementation 'org.springframework.boot:spring-boot-web-server' + testFixturesImplementation 'org.springframework.boot:spring-boot-autoconfigure' + testFixturesImplementation 'org.springframework.security:spring-security-config' + testFixturesImplementation 'tools.jackson.core:jackson-databind' + testFixturesImplementation 'org.springframework.security:spring-security-core' + testFixturesImplementation 'org.springframework.security:spring-security-web' + testFixturesImplementation 'io.projectreactor:reactor-core' + testFixturesImplementation libs.archunit.junit5 + testImplementation libs.archunit.junit5 // Jetty replaces Tomcat for this lane only. The exclusion is what makes the lane mean // something: with both on the classpath Boot starts Tomcat and the lane certifies nothing. diff --git a/src/adapter/inbound/web/gradle.lockfile b/src/adapter/inbound/web/gradle.lockfile index 6e6bc297..09461521 100644 --- a/src/adapter/inbound/web/gradle.lockfile +++ b/src/adapter/inbound/web/gradle.lockfile @@ -2,64 +2,64 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath -ch.qos.logback:logback-classic:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -ch.qos.logback:logback-core:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +ch.qos.logback:logback-classic:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +ch.qos.logback:logback-core:1.5.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath com.fasterxml.woodstox:woodstox-core:7.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml:classmate:1.7.3=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.fasterxml:classmate:1.7.3=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor com.github.docker-java:docker-java-api:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath com.github.docker-java:docker-java-transport-zerodep:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath com.github.docker-java:docker-java-transport:3.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath -com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs com.github.spotbugs:spotbugs:4.10.2=spotbugs com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.auto:auto-common:1.2.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.auto:auto-common:1.2.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs com.google.code.gson:gson:2.13.2=spotbugs -com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath com.google.errorprone:error_prone_annotations:2.41.0=spotbugs com.google.errorprone:error_prone_annotations:2.47.0=checkstyle -com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.guava:guava:33.5.0-jre=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.guava:guava:33.5.0-jre=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor com.google.guava:guava:33.6.0-jre=checkstyle -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins com.jayway.jsonpath:json-path:2.10.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.puppycrawl.tools:checkstyle:13.5.0=checkstyle -com.tngtech.archunit:archunit-junit5-api:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-api:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine:1.3.0=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.vaadin.external.google:android-json:0.0.20131108.vaadin1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath commons-beanutils:commons-beanutils:1.11.0=checkstyle commons-codec:commons-codec:1.19.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle commons-io:commons-io:2.20.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath commons-io:commons-io:2.21.0=spotbugs -commons-logging:commons-logging:1.3.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +commons-logging:commons-logging:1.3.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath info.picocli:picocli:4.7.7=checkstyle -io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -io.micrometer:micrometer-commons:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -io.micrometer:micrometer-observation:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +io.micrometer:micrometer-commons:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +io.micrometer:micrometer-observation:1.16.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath io.netty:netty-buffer:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath io.netty:netty-codec-base:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath io.netty:netty-codec-classes-quic:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath @@ -83,27 +83,27 @@ io.netty:netty-transport-native-unix-common:4.2.17.Final=webfluxContractTestComp io.netty:netty-transport:4.2.17.Final=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath io.projectreactor.netty:reactor-netty-core:1.3.7=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath io.projectreactor.netty:reactor-netty-http:1.3.7=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -io.projectreactor:reactor-core:3.8.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +io.projectreactor:reactor-core:3.8.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath io.projectreactor:reactor-test:3.8.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.swagger.core.v3:swagger-core-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.swagger.core.v3:swagger-models-jakarta:2.2.38=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath jakarta.enterprise:jakarta.enterprise.lang-model:4.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath jakarta.inject:jakarta.inject-api:2.0.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath jakarta.interceptor:jakarta.interceptor-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath -jakarta.servlet:jakarta.servlet-api:6.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath +jakarta.servlet:jakarta.servlet-api:6.1.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath jakarta.transaction:jakarta.transaction-api:2.0.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath -jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath jakarta.websocket:jakarta.websocket-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath jakarta.websocket:jakarta.websocket-client-api:2.2.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath -jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +javax.inject:javax.inject:1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor jaxen:jaxen:2.0.6=spotbugs net.bytebuddy:byte-buddy-agent:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -net.bytebuddy:byte-buddy:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath net.java.dev.jna:jna:5.18.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath net.minidev:accessors-smart:2.6.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath net.minidev:json-smart:2.6.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath @@ -111,24 +111,24 @@ net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs org.antlr:antlr4-runtime:4.13.2=checkstyle org.apache.bcel:bcel:6.12.0=spotbugs org.apache.commons:commons-compress:1.28.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath -org.apache.commons:commons-lang3:3.20.0=checkstyle,compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-lang3:3.20.0=checkstyle,compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.commons:commons-text:1.15.0=spotbugs org.apache.commons:commons-text:1.3=checkstyle org.apache.httpcomponents:httpclient:4.5.13=checkstyle org.apache.httpcomponents:httpcore:4.4.16=checkstyle -org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.apache.logging.log4j:log4j-core:2.25.5=spotbugs -org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.apache.maven.doxia:doxia-core:1.12.0=checkstyle org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle -org.apache.tomcat.embed:tomcat-embed-core:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-core:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.xbean:xbean-reflect:3.7=checkstyle -org.apiguardian:apiguardian-api:1.1.2=jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath -org.assertj:assertj-core:3.27.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath +org.assertj:assertj-core:3.27.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.awaitility:awaitility:4.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle @@ -167,25 +167,25 @@ org.eclipse.jetty:jetty-session:12.1.12=jettyCompatTestCompileClasspath,jettyCom org.eclipse.jetty:jetty-util:12.1.12=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath org.eclipse.jetty:jetty-xml:12.1.12=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath org.hamcrest:hamcrest:3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle -org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.jetbrains:annotations:17.0.0=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath -org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jettyCompatTestAnnotationProcessor,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestAnnotationProcessor,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestAnnotationProcessor,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jettyCompatTestAnnotationProcessor,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestAnnotationProcessor,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestAnnotationProcessor,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath org.junit.jupiter:junit-jupiter-params:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.junit.jupiter:junit-jupiter:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.junit.platform:junit-platform-commons:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.junit.platform:junit-platform-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath +org.junit.platform:junit-platform-commons:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.junit.platform:junit-platform-engine:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath -org.junit:junit-bom:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.junit:junit-bom:6.0.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs org.mockito:mockito-core:5.20.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,mockitoAgent,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.objenesis:objenesis:3.3=jettyCompatTestRuntimeClasspath,nginxProxyTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath,webfluxContractTestRuntimeClasspath -org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.openapitools:jackson-databind-nullable:0.2.6=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath org.osgi:org.osgi.resource:1.0.0=compileClasspath,jettyCompatTestCompileClasspath,nginxProxyTestCompileClasspath,testCompileClasspath,testkitCompileClasspath,webfluxContractTestCompileClasspath @@ -196,82 +196,82 @@ org.ow2.asm:asm-tree:9.10.1=jettyCompatTestCompileClasspath,jettyCompatTestRunti org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm:9.10.1=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,spotbugs org.ow2.asm:asm:9.7.1=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.pcollections:pcollections:4.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor -org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.pcollections:pcollections:4.0.1=annotationProcessor,jettyCompatTestAnnotationProcessor,nginxProxyTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor,webfluxContractTestAnnotationProcessor +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.reflections:reflections:0.10.2=checkstyle org.rnorth.duct-tape:duct-tape:1.0.8=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath org.skyscreamer:jsonassert:1.5.3=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.slf4j:slf4j-api:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.slf4j:slf4j-api:2.0.18=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j org.slf4j:slf4j-simple:2.0.18=checkstyle -org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springdoc:springdoc-openapi-starter-common:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor org.springframework.boot:spring-boot-http-codec:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-http-converter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-http-converter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-jetty:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath org.springframework.boot:spring-boot-netty:4.0.8=webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-reactor-netty:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-reactor:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-resttestclient:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-servlet:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-servlet:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-starter-jackson:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-starter-jetty-runtime:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath org.springframework.boot:spring-boot-starter-jetty:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath -org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-reactor-netty:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-starter-web:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-starter-web:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webflux:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.boot:spring-boot-web-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-tomcat:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.boot:spring-boot-web-server:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-webflux:4.0.8=webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath org.springframework.boot:spring-boot-webmvc-test:4.0.8=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-webmvc:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework.security:spring-security-config:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-oauth2-jose:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-oauth2-resource-server:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-webmvc:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot:4.0.8=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework.security:spring-security-config:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-oauth2-jose:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-oauth2-resource-server:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.security:spring-security-test:7.0.7=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-web:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.session:spring-session-core:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-aop:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-beans:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-context:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-core:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-expression:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-test:7.0.9=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-web:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-webflux:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.springframework:spring-webmvc:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-web:7.0.7=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.session:spring-session-core:4.0.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-aop:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-beans:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-context:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-core:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-expression:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-test:7.0.9=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-web:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-webflux:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.springframework:spring-webmvc:7.0.9=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.testcontainers:testcontainers-junit-jupiter:2.0.5=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath org.testcontainers:testcontainers:2.0.5=nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs org.xmlunit:xmlunit-core:2.10.4=jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -org.yaml:snakeyaml:2.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -tools.jackson.core:jackson-core:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath -tools.jackson.core:jackson-databind:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +org.yaml:snakeyaml:2.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +tools.jackson.core:jackson-core:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +tools.jackson.core:jackson-databind:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson.dataformat:jackson-dataformat-xml:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson:jackson-bom:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath +tools.jackson:jackson-bom:3.1.5=compileClasspath,jettyCompatTestCompileClasspath,jettyCompatTestRuntimeClasspath,nginxProxyTestCompileClasspath,nginxProxyTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath,webfluxContractTestCompileClasspath,webfluxContractTestRuntimeClasspath empty= diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenClassAnnotationCondition.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/ForbiddenMethodAnnotationCondition.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebArchitectureRules.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/arch/WebForbiddenTypeCatalog.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/BudgetFixtureProtocol.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/HttpBudgetFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/ReactiveBudgetFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/budget/WebBudgetContract.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebContractFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractRecording.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WebPlatformContractSuite.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireOutcome.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/contract/WireProbe.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureProtocol.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/FaultFixtureStore.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/HttpResponseLossFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/IdempotencyResponseLossContract.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ReactiveFaultFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/ResponseLossFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultInjector.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/fault/WebFaultPoint.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/ContractFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/mvc/WebContractAssertions.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/HttpOperationFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationFixtureSupport.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/OperationHttpContract.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/ReactiveOperationFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/operation/TestDurableOperationStore.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/HttpPipelineFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/PipelineOrderFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineOrderContract.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineRecorder.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/order/WebPipelineStage.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/GracefulShutdownProbe.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadAndShutdownContract.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/performance/WebLoadResult.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/proxy/ProxyFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/CountingRateLimiter.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/HttpThrottleFixture.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleControlController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureProtocol.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/ThrottleFixtureSupport.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/throttle/WebThrottleHttpContract.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/adapter/inbound/web/testkit/webflux/ReactiveContractFixtureController.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/BudgetFixtureApplication.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/PipelineOrderFixtureApplication.java diff --git a/src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java b/src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java similarity index 100% rename from src/adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java rename to src/adapter/inbound/web/src/testFixtures/java/dev/caskeleton/webtestkit/ThrottleFixtureApplication.java diff --git a/src/adapter/inbound/web/src/testkit/resources/performance/web-load-profile.yaml b/src/adapter/inbound/web/src/testFixtures/resources/performance/web-load-profile.yaml similarity index 100% rename from src/adapter/inbound/web/src/testkit/resources/performance/web-load-profile.yaml rename to src/adapter/inbound/web/src/testFixtures/resources/performance/web-load-profile.yaml diff --git a/src/adapter/inbound/websocket/build.gradle b/src/adapter/inbound/websocket/build.gradle index 93f7d9d6..8e3209da 100644 --- a/src/adapter/inbound/websocket/build.gradle +++ b/src/adapter/inbound/websocket/build.gradle @@ -1,3 +1,6 @@ +// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. +apply plugin: 'java-test-fixtures' + // Driving adapter: WebSocket (STOMP over SockJS) live-push channel (skeleton machinery, transport-only). // // W1 live-bus: LiveEventStompBroadcaster forwards only explicitly allowlisted, bounded primitive @@ -41,9 +44,9 @@ dependencies { // Pinned: no BOM manages it. The version matches what the toolchain already resolves for the // annotation processor, so the runtime and the processor cannot drift onto two protobuf // runtimes with different wire behaviour. - compileOnly 'com.google.protobuf:protobuf-java:4.33.2' + compileOnly libs.protobuf.java testImplementation 'tools.jackson.dataformat:jackson-dataformat-cbor' - testImplementation 'com.google.protobuf:protobuf-java:4.33.2' + testImplementation libs.protobuf.java annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' } @@ -52,7 +55,6 @@ dependencies { // tests and available to the composition root. A rule pack that only its own fixture tests import // is verified as library code and applied to nothing. strictTestLanes { - sourceSet('testkit') { compilesAgainst 'main' } // Jetty replaces Tomcat for this lane only. With both on one classpath Boot starts Tomcat and // the lane certifies the same container twice — which for a WebSocket matters more than for // HTTP, because upgrade handling, close-frame timing and idle handling are all container code. @@ -60,30 +62,35 @@ strictTestLanes { // only lane that needs Docker; folded into `test`, every developer's `check` would depend on a // container runtime and the usual end of that is a disabled test nobody notices. sourceSet('nginxWebSocketTest') { - compilesAgainst 'main', 'testkit' + compilesAgainst 'main', 'testFixtures' inherits 'implementation' } sourceSet('jettyWebSocketTest') { - compilesAgainst 'main', 'testkit' + compilesAgainst 'main', 'testFixtures' inherits 'implementation' } // The STOMP broker lane. Its own source set for the same reason the Nginx one has its own: it // is the only other lane that needs Docker, and folding it into `test` would make every // developer's `check` depend on a container runtime. sourceSet('brokerRelayTest') { - compilesAgainst 'main', 'testkit' + compilesAgainst 'main', 'testFixtures' inherits 'implementation' } } -testkitPublisher { - consumedBy 'test' - publishAs 'websocketTestkit' -} - dependencies { - testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' + // Declared rather than inherited: the `testkit` source set extended testImplementation, so the + // fixtures saw every test library this leaf declared without ever saying so (ADR-BUILD-001). + testFixturesImplementation 'org.assertj:assertj-core' + testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api' + testFixturesImplementation 'org.springframework.boot:spring-boot-test' + testFixturesImplementation 'org.springframework:spring-context' + testFixturesImplementation 'org.springframework:spring-web' + testFixturesImplementation 'org.springframework:spring-websocket' + testFixturesImplementation 'org.springframework.boot:spring-boot-autoconfigure' + testFixturesImplementation 'org.springframework.boot:spring-boot' + testFixturesImplementation libs.archunit.junit5 + testImplementation libs.archunit.junit5 testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-websocket' diff --git a/src/adapter/inbound/websocket/gradle.lockfile b/src/adapter/inbound/websocket/gradle.lockfile index 5c29e6ad..e283c5b7 100644 --- a/src/adapter/inbound/websocket/gradle.lockfile +++ b/src/adapter/inbound/websocket/gradle.lockfile @@ -2,74 +2,74 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath -ch.qos.logback:logback-classic:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -ch.qos.logback:logback-core:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.21=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml:classmate:1.7.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +ch.qos.logback:logback-classic:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +ch.qos.logback:logback-core:1.5.38=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml:classmate:1.7.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.github.docker-java:docker-java-api:3.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath com.github.docker-java:docker-java-transport-zerodep:3.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath com.github.docker-java:docker-java-transport:3.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath -com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs com.github.spotbugs:spotbugs:4.10.2=spotbugs com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto:auto-common:1.2.2=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto:auto-common:1.2.2=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs com.google.code.gson:gson:2.13.2=spotbugs -com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.errorprone:error_prone_annotations:2.38.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath com.google.errorprone:error_prone_annotations:2.41.0=spotbugs com.google.errorprone:error_prone_annotations:2.47.0=checkstyle -com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:failureaccess:1.0.3=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:guava:33.5.0-jre=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:failureaccess:1.0.3=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:guava:33.5.0-jre=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.guava:guava:33.6.0-jre=checkstyle -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,brokerRelayTestAnnotationProcessor,checkstyle,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins com.jayway.jsonpath:json-path:2.10.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.puppycrawl.tools:checkstyle:13.5.0=checkstyle -com.tngtech.archunit:archunit-junit5-api:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-api:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine:1.3.0=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.vaadin.external.google:android-json:0.0.20131108.vaadin1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-beanutils:commons-beanutils:1.11.0=checkstyle commons-codec:commons-codec:1.19.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle commons-io:commons-io:2.20.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath commons-io:commons-io:2.21.0=spotbugs -commons-logging:commons-logging:1.3.6=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-logging:commons-logging:1.3.6=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath info.picocli:picocli:4.7.7=checkstyle -io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.micrometer:micrometer-commons:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-observation:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor:reactor-core:3.8.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.micrometer:micrometer-commons:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-observation:1.16.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor:reactor-core:3.8.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.activation:jakarta.activation-api:2.1.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.annotation:jakarta.annotation-api:3.0.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:3.0.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.enterprise:jakarta.enterprise.lang-model:4.1.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.inject:jakarta.inject-api:2.0.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.interceptor:jakarta.interceptor-api:2.2.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.servlet:jakarta.servlet-api:6.1.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.transaction:jakarta.transaction-api:2.0.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath -jakarta.validation:jakarta.validation-api:3.1.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.validation:jakarta.validation-api:3.1.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.websocket:jakarta.websocket-api:2.2.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.websocket:jakarta.websocket-client-api:2.2.0=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +javax.inject:javax.inject:1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor jaxen:jaxen:2.0.6=spotbugs net.bytebuddy:byte-buddy-agent:1.17.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.bytebuddy:byte-buddy:1.17.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.java.dev.jna:jna:5.18.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath net.minidev:accessors-smart:2.6.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.minidev:json-smart:2.6.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath @@ -82,19 +82,19 @@ org.apache.commons:commons-text:1.15.0=spotbugs org.apache.commons:commons-text:1.3=checkstyle org.apache.httpcomponents:httpclient:4.5.13=checkstyle org.apache.httpcomponents:httpcore:4.4.16=checkstyle -org.apache.logging.log4j:log4j-api:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.logging.log4j:log4j-core:2.25.5=spotbugs -org.apache.logging.log4j:log4j-to-slf4j:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.25.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.maven.doxia:doxia-core:1.12.0=checkstyle org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle -org.apache.tomcat.embed:tomcat-embed-core:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.tomcat.embed:tomcat-embed-el:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-core:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-el:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.xbean:xbean-reflect:3.7=checkstyle -org.apiguardian:apiguardian-api:1.1.2=brokerRelayTestCompileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath -org.assertj:assertj-core:3.27.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=brokerRelayTestCompileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath +org.assertj:assertj-core:3.27.7=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.awaitility:awaitility:4.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle @@ -132,24 +132,24 @@ org.eclipse.jetty:jetty-session:12.1.12=jettyWebSocketTestCompileClasspath,jetty org.eclipse.jetty:jetty-util:12.1.12=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath org.eclipse.jetty:jetty-xml:12.1.12=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath org.hamcrest:hamcrest:3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hibernate.validator:hibernate-validator:9.0.1.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.hibernate.validator:hibernate-validator:9.0.1.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle -org.jboss.logging:jboss-logging:3.6.3.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jboss.logging:jboss-logging:3.6.3.Final=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.jetbrains:annotations:17.0.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath -org.jspecify:jspecify:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,checkstyle,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jspecify:jspecify:1.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,checkstyle,compileClasspath,jettyWebSocketTestAnnotationProcessor,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestAnnotationProcessor,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-params:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-commons:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-engine:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-commons:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-engine:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.junit:junit-bom:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit:junit-bom:6.0.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs org.mockito:mockito-core:5.20.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,mockitoAgent,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.objenesis:objenesis:3.3=brokerRelayTestRuntimeClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.osgi:org.osgi.annotation.bundle:2.0.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.annotation.versioning:1.1.2=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.resource:1.0.0=brokerRelayTestCompileClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,nginxWebSocketTestCompileClasspath,testCompileClasspath,testkitCompileClasspath @@ -160,63 +160,63 @@ org.ow2.asm:asm-tree:9.10.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTes org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm:9.10.1=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,spotbugs org.ow2.asm:asm:9.7.1=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.pcollections:pcollections:4.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -org.reactivestreams:reactive-streams:1.0.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.pcollections:pcollections:4.0.1=annotationProcessor,brokerRelayTestAnnotationProcessor,jettyWebSocketTestAnnotationProcessor,nginxWebSocketTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +org.reactivestreams:reactive-streams:1.0.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.reflections:reflections:0.10.2=checkstyle org.rnorth.duct-tape:duct-tape:1.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath org.skyscreamer:jsonassert:1.5.3=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:jul-to-slf4j:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:slf4j-api:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:slf4j-api:2.0.18=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j org.slf4j:slf4j-simple:2.0.18=checkstyle -org.springframework.boot:spring-boot-autoconfigure:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor -org.springframework.boot:spring-boot-http-converter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-http-converter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-jetty:4.0.8=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath org.springframework.boot:spring-boot-resttestclient:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-servlet:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-servlet:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-jackson:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jetty-runtime:4.0.8=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath org.springframework.boot:spring-boot-starter-jetty:4.0.8=jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath -org.springframework.boot:spring-boot-starter-logging:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-web-server:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-tomcat:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-validation:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-web-server:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc-test:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-aop:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-beans:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-context:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-core:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-expression:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-messaging:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-test:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-web:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-webflux:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-webmvc:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-websocket:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-webmvc:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-websocket:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot:4.0.8=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-aop:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-beans:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-context:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-core:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-expression:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-messaging:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-test:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-web:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-webflux:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-webmvc:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-websocket:7.0.9=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.testcontainers:testcontainers-junit-jupiter:2.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath org.testcontainers:testcontainers-rabbitmq:2.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath org.testcontainers:testcontainers:2.0.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs org.xmlunit:xmlunit-core:2.10.4=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.yaml:snakeyaml:2.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson.core:jackson-core:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson.core:jackson-databind:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.yaml:snakeyaml:2.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +tools.jackson.core:jackson-core:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +tools.jackson.core:jackson-databind:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson.dataformat:jackson-dataformat-cbor:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson:jackson-bom:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +tools.jackson:jackson-bom:3.1.5=brokerRelayTestCompileClasspath,brokerRelayTestRuntimeClasspath,compileClasspath,jettyWebSocketTestCompileClasspath,jettyWebSocketTestRuntimeClasspath,nginxWebSocketTestCompileClasspath,nginxWebSocketTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath empty= diff --git a/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java b/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java index bba4fe39..03365882 100644 --- a/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java +++ b/src/adapter/inbound/websocket/src/test/java/dev/caskeleton/adapter/inbound/websocket/advanced/codec/BinaryCodecBackendScopeTest.java @@ -7,6 +7,9 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -43,7 +46,62 @@ class BinaryCodecBackendScopeTest { } } + /** + * Reports whether the build file declares the coordinate in the given scope. + * + *

Two notations mean the same declaration. The literal {@code 'group:artifact:version'} form + * is matched directly; the version-catalog form is matched by resolving the leaf's {@code libs.*} + * accessors back to their modules. Matching only the literal string would have read {@code + * compileOnly libs.protobuf.java} as "not declared" — a scope contract reported as broken because + * versions moved into a catalog, which is a false positive expensive enough to be worth the + * parser. + */ private static boolean declares(String scope, String coordinate) { + if (declaresLiterally(scope, coordinate)) { + return true; + } + for (Map.Entry alias : catalogModules().entrySet()) { + if (alias.getValue().equals(coordinate) + && Pattern.compile( + "^\\s*" + scope + "\\s+" + Pattern.quote(alias.getKey()) + "\\s*$", + Pattern.MULTILINE) + .matcher(buildFile()) + .find()) { + return true; + } + } + return false; + } + + /** Version-catalog accessor to {@code group:artifact}, parsed from the catalog itself. */ + private static Map catalogModules() { + Path catalog = + WebSocketBuildModel.mainSourceRoot() + .getParent() + .getParent() + .getParent() + .getParent() + .getParent() + .getParent() + .resolve("gradle/libs.versions.toml"); + Map modules = new LinkedHashMap<>(); + if (!Files.isRegularFile(catalog)) { + return modules; + } + try { + Matcher entry = + Pattern.compile("(?m)^([A-Za-z0-9_-]+)\\s*=\\s*\\{[^}]*module\\s*=\\s*\"([^\"]+)\"") + .matcher(Files.readString(catalog)); + while (entry.find()) { + modules.put("libs." + entry.group(1).replace('-', '.').replace('_', '.'), entry.group(2)); + } + } catch (IOException failure) { + throw new UncheckedIOException(failure); + } + return modules; + } + + private static boolean declaresLiterally(String scope, String coordinate) { return Pattern.compile( "^\\s*" + scope + "\\s+'" + Pattern.quote(coordinate) + "(:|')", Pattern.MULTILINE) .matcher(buildFile()) diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/arch/WebSocketArchitectureRules.java diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultInjector.java diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/fault/WebSocketFaultPoint.java diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/SlowConsumerContract.java diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketAbuseContract.java diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketFixtureApplication.java diff --git a/src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java b/src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java similarity index 100% rename from src/adapter/inbound/websocket/src/testkit/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java rename to src/adapter/inbound/websocket/src/testFixtures/java/dev/caskeleton/adapter/inbound/websocket/testkit/runtime/WebSocketRuntimeContract.java diff --git a/src/adapter/outbound/fileserver/build.gradle b/src/adapter/outbound/fileserver/build.gradle index a34958ba..bec5be40 100644 --- a/src/adapter/outbound/fileserver/build.gradle +++ b/src/adapter/outbound/fileserver/build.gradle @@ -12,5 +12,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-autoconfigure' implementation 'org.slf4j:slf4j-api' + // Micrometer core for the metric sink that implements FileserverMetricsPort. It used to sit in + // the composition root next to the auto-configuration that wires it; but the class implements an + // application port over an external system, which makes it an outbound adapter and this its leaf + // (BOOT-017). The composition root still decides whether to wire it — that part is assembly. + implementation 'io.micrometer:micrometer-core' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' } diff --git a/src/adapter/outbound/fileserver/gradle.lockfile b/src/adapter/outbound/fileserver/gradle.lockfile index 17910be7..602b0296 100644 --- a/src/adapter/outbound/fileserver/gradle.lockfile +++ b/src/adapter/outbound/fileserver/gradle.lockfile @@ -41,6 +41,7 @@ info.picocli:picocli:4.7.7=checkstyle io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor io.micrometer:micrometer-commons:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-core:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-observation:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.activation:jakarta.activation-api:2.1.4=testCompileClasspath,testRuntimeClasspath jakarta.annotation:jakarta.annotation-api:3.0.0=testCompileClasspath,testRuntimeClasspath @@ -79,6 +80,7 @@ org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle org.codehaus.plexus:plexus-utils:3.3.0=checkstyle org.dom4j:dom4j:2.2.0=spotbugs org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=runtimeClasspath,testRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath org.junit.jupiter:junit-jupiter-api:6.0.3=testCompileClasspath,testRuntimeClasspath @@ -90,6 +92,7 @@ org.junit.platform:junit-platform-engine:6.0.3=testRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=testRuntimeClasspath org.junit:junit-bom:6.0.3=testCompileClasspath,testRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs +org.latencyutils:LatencyUtils:2.0.3=runtimeClasspath,testRuntimeClasspath org.mockito:mockito-core:5.20.0=mockitoAgent,testCompileClasspath,testRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=testCompileClasspath,testRuntimeClasspath org.objenesis:objenesis:3.3=testRuntimeClasspath diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/MicrometerFileserverMetrics.java b/src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability/MicrometerFileserverMetrics.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/MicrometerFileserverMetrics.java rename to src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability/MicrometerFileserverMetrics.java index 2f2fe7a4..1842d822 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/MicrometerFileserverMetrics.java +++ b/src/adapter/outbound/fileserver/src/main/java/dev/caskeleton/adapter/outbound/fileserver/platform/observability/MicrometerFileserverMetrics.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.autoconfigure.fileserver; +package dev.caskeleton.adapter.outbound.fileserver.platform.observability; import dev.caskeleton.application.fileserver.api.transfer.UploadProtocol; import dev.caskeleton.application.fileserver.observability.FileserverMetricsPort; diff --git a/src/adapter/outbound/httpclient/build.gradle b/src/adapter/outbound/httpclient/build.gradle index 47e011d4..68653b2f 100644 --- a/src/adapter/outbound/httpclient/build.gradle +++ b/src/adapter/outbound/httpclient/build.gradle @@ -1,3 +1,6 @@ +// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. +apply plugin: 'java-test-fixtures' + // Outbound HTTP Client Platform leaf — see // docs/superpowers/specs/2026-08-08-httpclient-platform-design.md (design package) and // docs/httpclient/repository-adaptation.md (how the design's 19 library modules map here). @@ -37,11 +40,11 @@ dependencies { // Resilience4j supplies the execution primitives only. HTTP retry *eligibility* is owned by // this module (design D-09) and never delegated to a generic retry library. - implementation 'io.github.resilience4j:resilience4j-retry:2.2.0' - implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0' - implementation 'io.github.resilience4j:resilience4j-ratelimiter:2.2.0' - implementation 'io.github.resilience4j:resilience4j-bulkhead:2.2.0' - implementation 'io.github.resilience4j:resilience4j-micrometer:2.2.0' + implementation libs.resilience4j.retry + implementation libs.resilience4j.circuitbreaker + implementation libs.resilience4j.ratelimiter + implementation libs.resilience4j.bulkhead + implementation libs.resilience4j.micrometer implementation 'org.springframework.security:spring-security-oauth2-client' implementation 'com.fasterxml.jackson.core:jackson-databind' @@ -61,14 +64,14 @@ dependencies { // Testkit dependencies (design §28.1 test topology). They are test-scoped so no production // module can depend on the testkit; the testkit source set inherits them by extending // testImplementation, and the test lanes use them directly. - testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' - testImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0' + testImplementation libs.mockwebserver + testImplementation libs.okhttp.tls testImplementation 'org.testcontainers:testcontainers' testImplementation 'org.testcontainers:testcontainers-junit-jupiter' testImplementation 'org.testcontainers:testcontainers-toxiproxy' testImplementation 'io.projectreactor:reactor-test' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE' + testImplementation libs.archunit.junit5 + testImplementation libs.blockhound } // The testkit is its own source set, not part of `test`, because three lanes consume it and only @@ -84,32 +87,37 @@ dependencies { // the default unit lane. strictTestLanes { // The testkit compiles against exactly what a test does: `implementation` inheritance runs - // through testImplementation, so this is the module's own dependencies plus the test libraries. - sourceSet('testkit') { compilesAgainst 'main' } - sourceSet('httpClientPerformanceTest') { compilesAgainst 'main', 'testkit' } - sourceSet('jmh') { compilesAgainst 'main', 'testkit' } -} - -// Every test lane compiles and runs against the testkit. -sourceSets.test { - compileClasspath += sourceSets.testkit.output - runtimeClasspath += sourceSets.testkit.output + sourceSet('httpClientPerformanceTest') { compilesAgainst 'main', 'testFixtures' } + sourceSet('jmh') { compilesAgainst 'main', 'testFixtures' } } dependencies { // Testkit dependencies (design §28.1 test topology). They stay off the production // configurations, so no production module can depend on the testkit. - testkitImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0' - testkitImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0' - testkitImplementation 'org.testcontainers:testcontainers' - testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter' - testkitImplementation 'org.testcontainers:testcontainers-toxiproxy' - testkitImplementation 'io.projectreactor:reactor-test' - testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testkitImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE' + // Declared rather than inherited. The `testkit` source set extended `testImplementation`, so the + // fixtures compiled against every test library this leaf happened to declare; moving to + // testFixtures makes that surface explicit (ADR-BUILD-001). These six were invisible before. + testFixturesImplementation 'io.micrometer:micrometer-core' + testFixturesImplementation 'org.springframework:spring-web' + testFixturesImplementation 'io.projectreactor:reactor-core' + testFixturesImplementation 'org.assertj:assertj-core' + testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api' + testFixturesImplementation 'org.slf4j:slf4j-api' + testFixturesImplementation libs.toxiproxy.java + // The Netty leak detector reads logback's own event types to catch a LEAK record. + testFixturesImplementation 'io.netty:netty-common' + testFixturesImplementation 'ch.qos.logback:logback-classic' + testFixturesImplementation libs.mockwebserver + testFixturesImplementation libs.okhttp.tls + testFixturesImplementation 'org.testcontainers:testcontainers' + testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter' + testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy' + testFixturesImplementation 'io.projectreactor:reactor-test' + testFixturesImplementation libs.archunit.junit5 + testFixturesImplementation libs.blockhound - jmhImplementation 'org.openjdk.jmh:jmh-core:1.37' - jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37' + jmhImplementation libs.jmh.core + jmhAnnotationProcessor libs.jmh.generator.annprocess } // UTF-8 is pinned for every JavaCompile task in the root build; this leaf no longer repeats it. @@ -176,6 +184,17 @@ strictTestLanes { description = 'Runs the cross-transport stable contract suite (design §28.2, §33).' customize = { test -> applyContractSelection(test) } } + // The same contract suite, run under the name the release gate and the support matrix quote for + // the repository's Spring 7 baseline. It used to be a hand-written `tasks.register(..., Test)` + // that copied the stable lane and dropped `failOnNoDiscoveredTests` — the sixth instance of the + // copy-paste failure the comment above records, and the one that mattered most, since three + // workflows call it by name as a release gate. Declared here so the convention supplies both + // fail-closed discovery and the refusal to report success on a run that executed nothing. + lane('spring70CompatibilityTest') { + tag = 'httpclient-contract' + description = 'Runs the contract suite on the repository Spring 7 baseline (design §29).' + customize = { test -> applyContractSelection(test) } + } lane('httpClientSecurityTest') { tag = 'httpclient-security' description = 'Runs the SSRF, credential-leak, and cardinality suite (design §28.5, §28.7).' @@ -246,13 +265,3 @@ tasks.named('check') { 'httpClientBlockHoundTest', 'spring62ApiSurfaceScan' } - -tasks.register('spring70CompatibilityTest', Test) { - group = 'verification' - description = 'Runs the contract suite on the repository Spring 7 baseline (design §29).' - testClassesDirs = sourceSets.test.output.classesDirs - classpath = sourceSets.test.runtimeClasspath - useJUnitPlatform { includeTags 'httpclient-contract' } - applyContractSelection(it) - outputs.upToDateWhen { false } -} diff --git a/src/adapter/outbound/httpclient/gradle.lockfile b/src/adapter/outbound/httpclient/gradle.lockfile index 5f186c01..be0ad990 100644 --- a/src/adapter/outbound/httpclient/gradle.lockfile +++ b/src/adapter/outbound/httpclient/gradle.lockfile @@ -2,125 +2,126 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath -ch.qos.logback:logback-classic:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -ch.qos.logback:logback-core:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.github.docker-java:docker-java-api:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.docker-java:docker-java-transport-zerodep:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.docker-java:docker-java-transport:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +ch.qos.logback:logback-classic:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +ch.qos.logback:logback-core:1.5.38=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.github.docker-java:docker-java-api:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.docker-java:docker-java-transport-zerodep:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.docker-java:docker-java-transport:3.7.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs com.github.spotbugs:spotbugs:4.10.2=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto:auto-common:1.2.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto:auto-common:1.2.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs -com.google.code.gson:gson:2.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.41.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.google.code.gson:gson:2.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.41.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.google.errorprone:error_prone_annotations:2.47.0=checkstyle -com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:guava:33.5.0-jre=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:guava:33.5.0-jre=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.guava:guava:33.6.0-jre=checkstyle -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins com.jayway.jsonpath:json-path:2.10.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.nimbusds:content-type:2.3=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.nimbusds:lang-tag:1.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.nimbusds:content-type:2.3=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.nimbusds:lang-tag:1.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.puppycrawl.tools:checkstyle:13.5.0=checkstyle -com.squareup.okhttp3:mockwebserver:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.squareup.okhttp3:okhttp-tls:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.squareup.okhttp3:okhttp:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.squareup.okio:okio-jvm:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.squareup.okio:okio:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-api:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.squareup.okhttp3:mockwebserver:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.squareup.okhttp3:okhttp-tls:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.squareup.okhttp3:okhttp:4.12.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.squareup.okio:okio-jvm:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.squareup.okio:okio:3.6.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-api:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine:1.3.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.vaadin.external.google:android-json:0.0.20131108.vaadin1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-beanutils:commons-beanutils:1.11.0=checkstyle -commons-codec:commons-codec:1.19.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-codec:commons-codec:1.19.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle -commons-io:commons-io:2.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-io:commons-io:2.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-io:commons-io:2.21.0=spotbugs -commons-logging:commons-logging:1.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-logging:commons-logging:1.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +eu.rekawek.toxiproxy:toxiproxy-java:2.1.7=testFixturesCompileClasspath,testFixturesRuntimeClasspath info.picocli:picocli:4.7.7=checkstyle -io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.github.resilience4j:resilience4j-bulkhead:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.github.resilience4j:resilience4j-core:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.github.resilience4j:resilience4j-micrometer:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.github.resilience4j:resilience4j-ratelimiter:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.github.resilience4j:resilience4j-retry:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.github.resilience4j:resilience4j-timelimiter:2.2.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-commons:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-core:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-observation:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-buffer:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-base:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-classes-quic:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-compression:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-http2:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-http3:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-http:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-codec-native-quic:4.2.17.Final=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -io.netty:netty-codec-socks:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-handler-proxy:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-handler:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-resolver-dns-native-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-resolver-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-resolver:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-transport-native-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.netty:netty-transport:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor.netty:reactor-netty-core:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor.netty:reactor-netty-http:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor.tools:blockhound:1.0.17.RELEASE=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor:reactor-core:3.8.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor:reactor-test:3.8.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.github.resilience4j:resilience4j-bulkhead:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.resilience4j:resilience4j-core:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.resilience4j:resilience4j-micrometer:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.resilience4j:resilience4j-ratelimiter:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.resilience4j:resilience4j-retry:2.2.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.resilience4j:resilience4j-timelimiter:2.2.0=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-commons:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-core:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-observation:1.16.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-buffer:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-base:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-classes-quic:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-compression:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-http2:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-http3:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-http:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-codec-native-quic:4.2.17.Final=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +io.netty:netty-codec-socks:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-handler-proxy:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-handler:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-resolver-dns-native-macos:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-resolver-dns:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-resolver:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-transport-classes-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-transport-native-epoll:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.netty:netty-transport:4.2.17.Final=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor.netty:reactor-netty-core:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor.netty:reactor-netty-http:1.3.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor.tools:blockhound:1.0.17.RELEASE=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor:reactor-core:3.8.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor:reactor-test:3.8.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.activation:jakarta.activation-api:2.1.4=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.annotation:jakarta.annotation-api:3.0.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +javax.inject:javax.inject:1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor jaxen:jaxen:2.0.6=spotbugs -junit:junit:4.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +junit:junit:4.13.2=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.bytebuddy:byte-buddy-agent:1.17.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.bytebuddy:byte-buddy:1.17.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.java.dev.jna:jna:5.18.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.minidev:accessors-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.minidev:json-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.java.dev.jna:jna:5.18.1=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.minidev:accessors-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.minidev:json-smart:2.6.0=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.sf.jopt-simple:jopt-simple:5.0.4=jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs org.antlr:antlr4-runtime:4.13.2=checkstyle org.apache.bcel:bcel:6.12.0=spotbugs -org.apache.commons:commons-compress:1.28.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.commons:commons-lang3:3.20.0=checkstyle,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-compress:1.28.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-lang3:3.20.0=checkstyle,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.commons:commons-math3:3.6.1=jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath org.apache.commons:commons-text:1.15.0=spotbugs org.apache.commons:commons-text:1.3=checkstyle -org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.httpcomponents.client5:httpclient5:5.5.2=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5:5.3.6=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.httpcomponents:httpclient:4.5.13=checkstyle org.apache.httpcomponents:httpcore:4.4.16=checkstyle org.apache.logging.log4j:log4j-api:2.25.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath @@ -134,16 +135,16 @@ org.apache.tomcat.embed:tomcat-embed-core:11.0.24=httpClientPerformanceTestCompi org.apache.tomcat.embed:tomcat-embed-el:11.0.24=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.xbean:xbean-reflect:3.7=checkstyle -org.apiguardian:apiguardian-api:1.1.2=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath -org.assertj:assertj-core:3.27.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath +org.assertj:assertj-core:3.27.7=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.awaitility:awaitility:4.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle org.codehaus.plexus:plexus-utils:3.3.0=checkstyle org.dom4j:dom4j:2.2.0=spotbugs -org.eclipse.jetty.compression:jetty-compression-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.eclipse.jetty.compression:jetty-compression-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.eclipse.jetty.http3:jetty-http3-client-transport:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.eclipse.jetty.http3:jetty-http3-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.eclipse.jetty.http3:jetty-http3-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath @@ -152,37 +153,37 @@ org.eclipse.jetty.quic:jetty-quic-api:12.1.12=compileClasspath,httpClientPerform org.eclipse.jetty.quic:jetty-quic-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.eclipse.jetty.quic:jetty-quic-common:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.eclipse.jetty.quic:jetty-quic-util:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.eclipse.jetty:jetty-alpn-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.eclipse.jetty:jetty-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.eclipse.jetty:jetty-http:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.eclipse.jetty:jetty-io:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.eclipse.jetty:jetty-util:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hamcrest:hamcrest-core:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hamcrest:hamcrest:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hdrhistogram:HdrHistogram:2.2.2=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.eclipse.jetty:jetty-alpn-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.eclipse.jetty:jetty-client:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.eclipse.jetty:jetty-http:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.eclipse.jetty:jetty-io:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.eclipse.jetty:jetty-util:12.1.12=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.hamcrest:hamcrest-core:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.hamcrest:hamcrest:3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle -org.jetbrains.kotlin:kotlin-stdlib-common:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jetbrains.kotlin:kotlin-stdlib:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jetbrains:annotations:17.0.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,httpClientPerformanceTestAnnotationProcessor,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:2.2.21=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jetbrains:annotations:17.0.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,httpClientPerformanceTestAnnotationProcessor,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-params:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-commons:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-engine:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-commons:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-engine:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.junit:junit-bom:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit:junit-bom:6.0.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs -org.latencyutils:LatencyUtils:2.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.latencyutils:LatencyUtils:2.0.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.mockito:mockito-core:5.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,mockitoAgent,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.objenesis:objenesis:3.3=httpClientPerformanceTestRuntimeClasspath,jmhRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.openjdk.jmh:jmh-core:1.37=jmhAnnotationProcessor,jmhCompileClasspath,jmhRuntimeClasspath org.openjdk.jmh:jmh-generator-annprocess:1.37=jmhAnnotationProcessor -org.opentest4j:opentest4j:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.osgi:org.osgi.annotation.bundle:2.0.0=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.annotation.versioning:1.1.2=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.resource:1.0.0=httpClientPerformanceTestCompileClasspath,jmhCompileClasspath,testCompileClasspath,testkitCompileClasspath @@ -192,17 +193,17 @@ org.ow2.asm:asm-commons:9.10.1=spotbugs org.ow2.asm:asm-tree:9.10.1=spotbugs org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm:9.10.1=spotbugs -org.ow2.asm:asm:9.7.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.pcollections:pcollections:4.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -org.reactivestreams:reactive-streams:1.0.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.ow2.asm:asm:9.7.1=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.pcollections:pcollections:4.0.1=annotationProcessor,httpClientPerformanceTestAnnotationProcessor,jmhAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.reflections:reflections:0.10.2=checkstyle -org.rnorth.duct-tape:duct-tape:1.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.rnorth.duct-tape:duct-tape:1.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.skyscreamer:jsonassert:1.5.3=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.slf4j:jul-to-slf4j:2.0.18=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:slf4j-api:2.0.18=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:slf4j-api:2.0.18=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j org.slf4j:slf4j-simple:2.0.18=checkstyle -org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-http-converter:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-jackson:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-resttestclient:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath @@ -222,28 +223,28 @@ org.springframework.boot:spring-boot-tomcat:4.0.8=httpClientPerformanceTestCompi org.springframework.boot:spring-boot-web-server:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc-test:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc:4.0.8=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.security:spring-security-web:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-aop:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-beans:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-context:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-core:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-expression:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot:4.0.8=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.security:spring-security-web:7.0.7=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-aop:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-beans:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-context:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-core:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-expression:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-test:7.0.9=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-web:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-webflux:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-web:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-webflux:7.0.9=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-webmvc:7.0.9=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-junit-jupiter:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-toxiproxy:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-junit-jupiter:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-toxiproxy:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers:2.0.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs org.xmlunit:xmlunit-core:2.10.4=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.yaml:snakeyaml:2.5=httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson.core:jackson-core:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson.core:jackson-databind:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -tools.jackson:jackson-bom:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +tools.jackson.core:jackson-core:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +tools.jackson.core:jackson-databind:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +tools.jackson:jackson-bom:3.1.5=compileClasspath,httpClientPerformanceTestCompileClasspath,httpClientPerformanceTestRuntimeClasspath,jmhCompileClasspath,jmhRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath empty= diff --git a/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java b/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java index 76e10b27..204f9bf3 100644 --- a/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java +++ b/src/adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java @@ -11,20 +11,27 @@ import com.tngtech.archunit.core.importer.ImportOption; * rule classes silently disagreeing about it is how a boundary stops being enforced. * *

ArchUnit's {@code DO_NOT_INCLUDE_TESTS} recognises the conventional test output locations, and - * the testkit is no longer in one: it is its own source set, so it compiles to {@code - * build/classes/java/testkit}. Left in, the fixtures would be imported as production code and every + * the fixtures are not in one: they are their own source set, compiling to {@code + * build/classes/java/testFixtures}. Left in, they would be imported as production code and every * rule here would be asserted against them — starting with the one that says production code never - * depends on the testkit, which the testkit itself trivially does. + * depends on the fixtures, which the fixtures themselves trivially do. + * + *

The path moved when this leaf adopted {@code java-test-fixtures} (ADR-BUILD-001) and the rule + * caught it: the exclusion still named {@code /classes/java/testkit/}, so the fixtures were suddenly + * production and the boundary test failed on the first run. That is the check working — an import + * filter that silently stops matching is a rule asserted against the wrong corpus. */ public final class PlatformClasses { - private static final ImportOption NOT_THE_TESTKIT_SOURCE_SET = - location -> !location.contains("/classes/java/testkit/"); + private static final ImportOption NOT_THE_FIXTURES_SOURCE_SET = + location -> + !location.contains("/classes/java/testFixtures/") + && !location.contains("test-fixtures"); private static final JavaClasses PRODUCTION = new ClassFileImporter() .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .withImportOption(NOT_THE_TESTKIT_SOURCE_SET) + .withImportOption(NOT_THE_FIXTURES_SOURCE_SET) .importPackages("dev.caskeleton.adapter.outbound.httpclient"); private PlatformClasses() {} @@ -32,7 +39,7 @@ public final class PlatformClasses { /** * Returns the platform's production classes. * - * @return every compiled class of this module that is neither a test nor a testkit fixture + * @return every compiled class of this module that is neither a test nor a test fixture */ public static JavaClasses production() { return PRODUCTION; diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/BlockingTransportContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ClientProfiles.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/CoreFixtures.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargetSecurityContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/DynamicTargets.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/EventPayload.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/Http2FailureFixture.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/HttpClientContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/InvalidPostClient.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MissingKeyClient.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MixedSignatureClient.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/MockHttpServer.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NettyLeakDetectionExtension.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/NoopLifecycleListener.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/OAuth2Fixture.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ObservabilityContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ProxyFixture.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveEventsClient.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTestGateways.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ReactiveTransportContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordedHttpRequest.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingCredentialProvider.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingResilienceComponents.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RecordingSleeper.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ResourceLifecycleContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetryContexts.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/RetrySafetyContract.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/StatefulUpstream.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TestGateways.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsFixture.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/TlsMaterials.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/ToxiproxyFixture.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UnsafeRetryClient.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UserResponse.java diff --git a/src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java b/src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java similarity index 100% rename from src/adapter/outbound/httpclient/src/testkit/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java rename to src/adapter/outbound/httpclient/src/testFixtures/java/dev/caskeleton/adapter/outbound/httpclient/testkit/UsersClient.java diff --git a/src/adapter/outbound/identifier/build.gradle b/src/adapter/outbound/identifier/build.gradle index eed16a2c..3f4c92e8 100644 --- a/src/adapter/outbound/identifier/build.gradle +++ b/src/adapter/outbound/identifier/build.gradle @@ -6,7 +6,7 @@ plugins { dependencies { implementation project(':application-core') - testImplementation 'org.spockframework:spock-core:2.4-groovy-5.0' + testImplementation libs.spock.core.groovy5 } // Pin UTF-8 so non-ASCII (Korean) Spock spec names build on any host. See README. diff --git a/src/adapter/outbound/messaging/build.gradle b/src/adapter/outbound/messaging/build.gradle index d23001ca..c7313587 100644 --- a/src/adapter/outbound/messaging/build.gradle +++ b/src/adapter/outbound/messaging/build.gradle @@ -6,7 +6,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-autoconfigure' implementation 'org.springframework.boot:spring-boot-starter-json' - implementation('com.networknt:json-schema-validator:3.0.2') { + implementation(libs.json.schema.validator) { exclude group: 'com.fasterxml.jackson.dataformat', module: 'jackson-dataformat-yaml' } implementation 'org.slf4j:slf4j-api' @@ -48,9 +48,9 @@ tasks.register('verifyJsonSchemaRuntimeGraph') { "Messaging JSON runtime contains forbidden Jackson 2/YAML modules: ${forbidden}") } [ - 'com.networknt:json-schema-validator:3.0.2', - 'tools.jackson.core:jackson-core:3.0.2', - 'tools.jackson.core:jackson-databind:3.0.2' + libs.json.schema.validator, + libs.jackson3.core, + libs.jackson3.databind ].each { String required -> if (!modules.contains(required)) { throw new GradleException( diff --git a/src/adapter/outbound/notification/build.gradle b/src/adapter/outbound/notification/build.gradle index 517a9501..06b95495 100644 --- a/src/adapter/outbound/notification/build.gradle +++ b/src/adapter/outbound/notification/build.gradle @@ -7,6 +7,13 @@ dependencies { implementation 'org.springframework:spring-web' // Slack webhook client (RestClient) implementation 'org.slf4j:slf4j-api' + // Micrometer core for the metric sink that implements NotificationMetricsPort. The sink used + // to live in the composition root because the MeterRegistry does; but a MeterRegistry is an + // external system like any other, and an implementation of an application port is an + // outbound adapter wherever its dependency happens to be configured (BOOT-017). Its logging + // counterpart, LoggingNotificationMetrics, was already here. + implementation 'io.micrometer:micrometer-core' + // Notification Delivery Platform. // - mail: the SMTP provider adapter is built on JavaMailSender/MimeMessageHelper, which is where // multipart/alternative, inline resources and header validation already live. Rebuilding MIME @@ -32,7 +39,7 @@ dependencies { // adapter that renders strings and never serves a request. implementation 'org.thymeleaf:thymeleaf' - implementation('com.networknt:json-schema-validator:3.0.2') { + implementation(libs.json.schema.validator) { exclude group: 'tools.jackson.dataformat', module: 'jackson-dataformat-yaml' exclude group: 'com.fasterxml.jackson.dataformat', module: 'jackson-dataformat-yaml' } diff --git a/src/adapter/outbound/notification/gradle.lockfile b/src/adapter/outbound/notification/gradle.lockfile index b30c50f9..b7a0b5fe 100644 --- a/src/adapter/outbound/notification/gradle.lockfile +++ b/src/adapter/outbound/notification/gradle.lockfile @@ -43,6 +43,7 @@ info.picocli:picocli:4.7.7=checkstyle io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor io.micrometer:micrometer-commons:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-core:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-observation:1.16.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.projectreactor:reactor-core:3.8.7=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath io.projectreactor:reactor-test:3.8.7=testCompileClasspath,testRuntimeClasspath @@ -88,6 +89,7 @@ org.dom4j:dom4j:2.2.0=spotbugs org.eclipse.angus:angus-activation:2.0.3=runtimeClasspath,testRuntimeClasspath org.eclipse.angus:angus-mail:2.0.5=runtimeClasspath,testRuntimeClasspath org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=runtimeClasspath,testRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle org.javassist:javassist:3.29.0-GA=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath @@ -100,6 +102,7 @@ org.junit.platform:junit-platform-engine:6.0.3=testRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=testRuntimeClasspath org.junit:junit-bom:6.0.3=testCompileClasspath,testRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs +org.latencyutils:LatencyUtils:2.0.3=runtimeClasspath,testRuntimeClasspath org.mockito:mockito-core:5.20.0=mockitoAgent,testCompileClasspath,testRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=testCompileClasspath,testRuntimeClasspath org.objenesis:objenesis:3.3=testRuntimeClasspath diff --git a/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java new file mode 100644 index 00000000..18e586dc --- /dev/null +++ b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderCallbackAdapterRegistry.java @@ -0,0 +1,62 @@ +package dev.caskeleton.adapter.outbound.notification.platform.callback; + +import dev.caskeleton.application.notification.platform.api.ProviderProfileId; +import dev.caskeleton.application.notification.platform.callback.CallbackLimits; +import dev.caskeleton.application.notification.platform.callback.ProviderCallbackAdapter; +import dev.caskeleton.application.notification.platform.callback.ProviderCallbackAdapterRegistry; +import java.util.Map; +import java.util.Objects; + +/** + * Profile-to-adapter lookup over a map fixed at assembly time. + * + *

This used to be an anonymous class inside the composition root's registries configuration + * (BOOT-017). Two things were wrong with that, and only one of them was placement. + * + *

The placement: an implementation of an {@code application-core} port is an outbound adapter, + * and building it from a map the composition root assembled does not change what it is. The + * composition root still decides the map's contents; what it no longer holds is the lookup, the + * refusal and the limits. + * + *

The other, which the move surfaced: {@link #require} runs per callback request, not + * at startup. While it lived in bootstrap it was briefly migrated to the startup-failure factory + * along with that leaf's genuine startup guards, which would have classified an unknown profile in + * an inbound request as a configuration error carrying a JVM exit code. Here the contract is the + * one the interface documents and every sibling {@code require(...)} registry in this repository + * uses. + * + *

Refusing an unknown profile is a security property rather than a convenience: an endpoint that + * accepted any profile name would let a caller choose which signature check runs. + */ +public final class MapProviderCallbackAdapterRegistry implements ProviderCallbackAdapterRegistry { + + private final Map byProfile; + private final CallbackLimits limits; + + /** + * Creates the registry. + * + * @param byProfile the profiles this deployment actually configured, copied defensively + * @param limits the limits every callback endpoint of this platform enforces + */ + public MapProviderCallbackAdapterRegistry( + Map byProfile, CallbackLimits limits) { + this.byProfile = Map.copyOf(Objects.requireNonNull(byProfile, "byProfile")); + this.limits = Objects.requireNonNull(limits, "limits"); + } + + @Override + public ProviderCallbackAdapter require(ProviderProfileId profileId) { + Objects.requireNonNull(profileId, "profileId"); + ProviderCallbackAdapter adapter = byProfile.get(profileId); + if (adapter == null) { + throw new IllegalStateException("no callback adapter registered for the profile"); + } + return adapter; + } + + @Override + public CallbackLimits limitsFor(ProviderProfileId profileId) { + return limits; + } +} diff --git a/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java new file mode 100644 index 00000000..09be9bb4 --- /dev/null +++ b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/callback/MapProviderEventProjectorRegistry.java @@ -0,0 +1,37 @@ +package dev.caskeleton.adapter.outbound.notification.platform.callback; + +import dev.caskeleton.application.notification.platform.api.ProviderId; +import dev.caskeleton.application.notification.platform.callback.ProviderEventProjector; +import dev.caskeleton.application.notification.platform.callback.ProviderEventProjectorRegistry; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Provider-to-projector lookup over a map fixed at assembly time. + * + *

Moved out of the composition root alongside {@link MapProviderCallbackAdapterRegistry} + * (BOOT-017). It was a lambda there rather than an anonymous class, which is why the ArchUnit rule + * that caught its sibling could not see it — a lambda implementing an interface leaves no class for + * the rule to inspect. Leaving it behind would have meant the boundary held only where a tool + * happened to be able to check it, which is a worse state than not having the rule: the code would + * look compliant and be inconsistent. + */ +public final class MapProviderEventProjectorRegistry implements ProviderEventProjectorRegistry { + + private final Map byProvider; + + /** + * Creates the registry. + * + * @param byProvider the projectors this deployment declared, copied defensively + */ + public MapProviderEventProjectorRegistry(Map byProvider) { + this.byProvider = Map.copyOf(Objects.requireNonNull(byProvider, "byProvider")); + } + + @Override + public Optional find(ProviderId providerId) { + return Optional.ofNullable(byProvider.get(providerId)); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/MicrometerNotificationMetrics.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java rename to src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/MicrometerNotificationMetrics.java index 0d906d14..2a179515 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/MicrometerNotificationMetrics.java +++ b/src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/observation/MicrometerNotificationMetrics.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification.observation; +package dev.caskeleton.adapter.outbound.notification.platform.observation; import dev.caskeleton.application.notification.platform.observation.CardinalityGuard; import dev.caskeleton.application.notification.platform.observation.NotificationMetricsPort; diff --git a/src/adapter/outbound/objectstorage/build.gradle b/src/adapter/outbound/objectstorage/build.gradle index ad99e57d..6066003a 100644 --- a/src/adapter/outbound/objectstorage/build.gradle +++ b/src/adapter/outbound/objectstorage/build.gradle @@ -38,8 +38,8 @@ dependencies { testImplementation 'org.testcontainers:testcontainers' testImplementation 'org.testcontainers:testcontainers-junit-jupiter' testImplementation 'org.testcontainers:testcontainers-toxiproxy' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testImplementation 'net.jqwik:jqwik:1.9.1' + testImplementation libs.archunit.junit5 + testImplementation libs.jqwik } def objectStorageReadinessRegistry = rootProject.projectDir.parentFile.toPath() diff --git a/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java b/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java index 9bb2f579..07516605 100644 --- a/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java +++ b/src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/config/ObjectStorageBindingCompiler.java @@ -9,6 +9,7 @@ import dev.caskeleton.adapter.outbound.objectstorage.provider.ObjectStorageProvi import dev.caskeleton.application.objectstorage.identity.ObjectDestinationId; import dev.caskeleton.application.objectstorage.model.ObjectCapabilityRequirement; import dev.caskeleton.application.objectstorage.model.ObjectPublicationRequirement; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.nio.file.Path; import java.time.Clock; import java.util.LinkedHashMap; @@ -234,10 +235,17 @@ public final class ObjectStorageBindingCompiler { } } + /** + * Reports whether this adapter is compiling bindings for a production deployment. + * + *

Delegates to the repository-wide profile vocabulary rather than matching literals here. This + * method used to accept {@code production} alongside {@code prod}, which no deployable artifact + * could ever present — the composition root requires exactly one of {@code local}, {@code dev}, + * {@code prod} and refuses anything else — so the extra spelling only ever diverged from the six + * other callers of the same question in slice tests (BOOT-002). + */ private boolean productionProfileActive() { - return activeProfiles.stream() - .map(profile -> profile.toLowerCase(Locale.ROOT)) - .anyMatch(profile -> profile.equals("prod") || profile.equals("production")); + return RuntimeEnvironment.isProductionActive(activeProfiles); } private static String canonicalId(String label, String value) { diff --git a/src/adapter/outbound/persistence-jpa/build.gradle b/src/adapter/outbound/persistence-jpa/build.gradle index d57ec352..6f103539 100644 --- a/src/adapter/outbound/persistence-jpa/build.gradle +++ b/src/adapter/outbound/persistence-jpa/build.gradle @@ -1,3 +1,6 @@ +// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. +apply plugin: 'java-test-fixtures' + // JPA persistence adapter — merged RDBMS base + PostgreSQL vendor module. // Owns JPA entities, Spring Data repositories, mappers, transaction/audit/lock/outbox port // implementations, and the vendor-neutral SPI interfaces (OutboxClaimRepository / @@ -14,23 +17,13 @@ // the design's "no production module depends on the testkit" guarantee without a new Gradle project. strictTestLanes { sourceSet('postgresqlIntegrationTest') { - compilesAgainst 'main' + // 'testFixtures' as well as 'main': the integration lane consumed the testkit through the + // convention's `consumedBy 'test', 'postgresqlIntegrationTest'`, and java-test-fixtures only + // wires `test` on its own. The lane has to say so (ADR-BUILD-001). + compilesAgainst 'main', 'testFixtures' inherits 'implementation', 'compileOnly', 'runtimeOnly', 'annotationProcessor' } - sourceSet('testkit') { compilesAgainst 'main' } - sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testkit' } -} - -// The testkit as a consumable artifact. -// -// The rule pack in `testkit` was only ever exercised by its own fixture tests: nothing imported the -// production graph and ran the rules against it, so "controller must not expose an entity" and -// "domain must not depend on Hibernate" were verified as library code and applied to nothing. The -// composition root is the only place that can see every runtime leaf at once, so it is where the -// production suite belongs — and it needs the rules. -testkitPublisher { - consumedBy 'test', 'postgresqlIntegrationTest' - publishAs 'jpaTestkit' + sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testFixtures' } } ext.jpaPostgreSqlEvidenceImage = 'postgres:16-alpine' @@ -71,19 +64,31 @@ dependencies { testImplementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' testImplementation 'org.hibernate.orm:hibernate-envers' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' + testImplementation libs.archunit.junit5 postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-postgresql' postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter' postgresqlIntegrationTestImplementation 'org.testcontainers:testcontainers-toxiproxy' postgresqlIntegrationTestRuntimeOnly 'org.postgresql:postgresql' - testkitImplementation 'org.testcontainers:testcontainers' - testkitImplementation 'org.testcontainers:testcontainers-postgresql' - testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter' - testkitImplementation 'org.testcontainers:testcontainers-toxiproxy' - testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testkitRuntimeOnly 'org.postgresql:postgresql' + // Declared rather than inherited: the `testkit` source set extended testImplementation, so the + // fixtures compiled against every test library this leaf declared without stating any of them + // (ADR-BUILD-001). Listing them makes the fixtures' own surface reviewable. + testFixturesImplementation 'jakarta.persistence:jakarta.persistence-api' + testFixturesImplementation 'org.hibernate.orm:hibernate-envers' + testFixturesImplementation 'org.flywaydb:flyway-core' + testFixturesImplementation 'org.springframework:spring-jdbc' + // The rule pack names Spring Data's Repository marker in a rule; it needs the type on the + // compile classpath even though it never calls it. + testFixturesImplementation 'org.springframework.data:spring-data-commons' + testFixturesImplementation 'org.junit.jupiter:junit-jupiter-api' + testFixturesImplementation libs.toxiproxy.java + testFixturesImplementation 'org.testcontainers:testcontainers' + testFixturesImplementation 'org.testcontainers:testcontainers-postgresql' + testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter' + testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy' + testFixturesImplementation libs.archunit.junit5 + testFixturesRuntimeOnly 'org.postgresql:postgresql' // The performance lane starts its own servers: pool saturation is only observable against a // real database, because the thing being measured is what happens when every connection to it @@ -354,3 +359,4 @@ apiSurface { } apply from: rootProject.file('gradle/jpa-evidence.gradle') + diff --git a/src/adapter/outbound/persistence-jpa/gradle.lockfile b/src/adapter/outbound/persistence-jpa/gradle.lockfile index 8a34d2fd..e55a23e8 100644 --- a/src/adapter/outbound/persistence-jpa/gradle.lockfile +++ b/src/adapter/outbound/persistence-jpa/gradle.lockfile @@ -2,95 +2,96 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath -ch.qos.logback:logback-classic:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -ch.qos.logback:logback-core:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml:classmate:1.7.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.github.docker-java:docker-java-api:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.docker-java:docker-java-transport-zerodep:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.docker-java:docker-java-transport:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +ch.qos.logback:logback-classic:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +ch.qos.logback:logback-core:1.5.38=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml:classmate:1.7.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.github.docker-java:docker-java-api:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.docker-java:docker-java-transport-zerodep:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.docker-java:docker-java-transport:3.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs com.github.spotbugs:spotbugs:4.10.2=spotbugs com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto:auto-common:1.2.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto:auto-common:1.2.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs -com.google.code.gson:gson:2.13.2=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testkitCompileClasspath,testkitRuntimeClasspath -com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.code.gson:gson:2.13.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,testCompileClasspath -com.google.errorprone:error_prone_annotations:2.41.0=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testkitCompileClasspath,testkitRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.41.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.google.errorprone:error_prone_annotations:2.47.0=checkstyle -com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:guava:33.5.0-jre=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:guava:33.5.0-jre=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.guava:guava:33.6.0-jre=checkstyle -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.h2database:h2:2.4.240=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.h2database:h2:2.4.240=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins com.jayway.jsonpath:json-path:2.10.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.mysema.commons:mysema-commons-lang:0.2.4=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.puppycrawl.tools:checkstyle:13.5.0=checkstyle com.querydsl:querydsl-core:5.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.querydsl:querydsl-jpa:5.1.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.sun.istack:istack-commons-runtime:4.1.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-api:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.sun.istack:istack-commons-runtime:4.1.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-api:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine:1.3.0=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.vaadin.external.google:android-json:0.0.20131108.vaadin1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.zaxxer:HikariCP:7.0.2=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.zaxxer:HikariCP:7.0.2=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-beanutils:commons-beanutils:1.11.0=checkstyle -commons-codec:commons-codec:1.19.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-codec:commons-codec:1.19.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle -commons-io:commons-io:2.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-io:commons-io:2.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-io:commons-io:2.21.0=spotbugs -commons-logging:commons-logging:1.3.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-logging:commons-logging:1.3.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +eu.rekawek.toxiproxy:toxiproxy-java:2.1.7=testFixturesCompileClasspath,testFixturesRuntimeClasspath info.picocli:picocli:4.7.7=checkstyle -io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.micrometer:micrometer-commons:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-core:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-observation:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor:reactor-core:3.8.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.smallrye:jandex:3.3.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -jakarta.activation:jakarta.activation-api:2.1.4=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.inject:jakarta.inject-api:2.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -jakarta.persistence:jakarta.persistence-api:3.2.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.micrometer:micrometer-commons:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-core:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-observation:1.16.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor:reactor-core:3.8.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.smallrye:jandex:3.3.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.4=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.inject:jakarta.inject-api:2.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +jakarta.persistence:jakarta.persistence-api:3.2.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +javax.inject:javax.inject:1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor jaxen:jaxen:2.0.6=spotbugs net.bytebuddy:byte-buddy-agent:1.17.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.bytebuddy:byte-buddy:1.17.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.java.dev.jna:jna:5.18.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.java.dev.jna:jna:5.18.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.minidev:accessors-smart:2.6.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.minidev:json-smart:2.6.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs -org.antlr:antlr4-runtime:4.13.2=checkstyle,compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.antlr:antlr4-runtime:4.13.2=checkstyle,compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.bcel:bcel:6.12.0=spotbugs -org.apache.commons:commons-compress:1.28.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.commons:commons-lang3:3.20.0=checkstyle,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-compress:1.28.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-lang3:3.20.0=checkstyle,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,spotbugs,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.commons:commons-text:1.15.0=spotbugs org.apache.commons:commons-text:1.3=checkstyle org.apache.httpcomponents:httpclient:4.5.13=checkstyle org.apache.httpcomponents:httpcore:4.4.16=checkstyle -org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.logging.log4j:log4j-core:2.25.5=spotbugs -org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.maven.doxia:doxia-core:1.12.0=checkstyle org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle @@ -99,45 +100,45 @@ org.apache.tomcat.embed:tomcat-embed-core:11.0.24=jpaPlatformPerformanceTestComp org.apache.tomcat.embed:tomcat-embed-el:11.0.24=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.xbean:xbean-reflect:3.7=checkstyle -org.apiguardian:apiguardian-api:1.1.2=jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath -org.aspectj:aspectjweaver:1.9.25.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testFixturesCompileClasspath,testkitCompileClasspath +org.aspectj:aspectjweaver:1.9.25.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.assertj:assertj-core:3.27.7=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.awaitility:awaitility:4.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.checkerframework:checker-qual:3.55.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.checkerframework:checker-qual:3.55.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle org.codehaus.plexus:plexus-utils:3.3.0=checkstyle org.dom4j:dom4j:2.2.0=spotbugs -org.eclipse.angus:angus-activation:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.flywaydb:flyway-core:11.14.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.flywaydb:flyway-database-postgresql:11.14.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.glassfish.jaxb:jaxb-core:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.glassfish.jaxb:jaxb-runtime:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.glassfish.jaxb:txw2:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.eclipse.angus:angus-activation:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.flywaydb:flyway-core:11.14.1=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.flywaydb:flyway-database-postgresql:11.14.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.glassfish.jaxb:jaxb-core:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.glassfish.jaxb:jaxb-runtime:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.glassfish.jaxb:txw2:4.0.9=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.hamcrest:hamcrest:3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hdrhistogram:HdrHistogram:2.2.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.hibernate.models:hibernate-models:1.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.hibernate.orm:hibernate-core:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hibernate.orm:hibernate-envers:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.hibernate.models:hibernate-models:1.0.1=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.hibernate.orm:hibernate-core:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.hibernate.orm:hibernate-envers:7.2.24.Final=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle -org.jboss.logging:jboss-logging:3.6.3.Final=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.jetbrains:annotations:17.0.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jpaPlatformPerformanceTestAnnotationProcessor,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestAnnotationProcessor,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jboss.logging:jboss-logging:3.6.3.Final=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.jetbrains:annotations:17.0.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,jpaPlatformPerformanceTestAnnotationProcessor,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestAnnotationProcessor,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-params:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-commons:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-engine:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-commons:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-engine:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.junit:junit-bom:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit:junit-bom:6.0.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs -org.latencyutils:LatencyUtils:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.latencyutils:LatencyUtils:2.0.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.mockito:mockito-core:5.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,mockitoAgent,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.objenesis:objenesis:3.3=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.resource:1.0.0=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,postgresqlIntegrationTestCompileClasspath,testCompileClasspath,testkitCompileClasspath @@ -148,76 +149,76 @@ org.ow2.asm:asm-tree:9.10.1=spotbugs org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm:9.10.1=spotbugs org.ow2.asm:asm:9.7.1=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.pcollections:pcollections:4.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -org.postgresql:postgresql:42.7.13=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.pcollections:pcollections:4.0.1=annotationProcessor,jpaPlatformPerformanceTestAnnotationProcessor,postgresqlIntegrationTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +org.postgresql:postgresql:42.7.13=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.reflections:reflections:0.10.2=checkstyle -org.rnorth.duct-tape:duct-tape:1.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.rnorth.duct-tape:duct-tape:1.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.skyscreamer:jsonassert:1.5.3=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:slf4j-api:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:slf4j-api:2.0.18=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j org.slf4j:slf4j-simple:2.0.18=checkstyle -org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor -org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-hibernate:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-hibernate:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-http-converter:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-jackson:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-resttestclient:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-servlet:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-flyway:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jackson:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-jdbc:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-tomcat:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-tomcat:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-web-server:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc-test:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc:4.0.8=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.data:spring-data-commons:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.data:spring-data-jpa:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.integration:spring-integration-core:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.integration:spring-integration-jdbc:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-aop:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-aspects:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-beans:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-context:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-core:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-expression:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-jdbc:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-messaging:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-orm:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot:4.0.8=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.data:spring-data-commons:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.data:spring-data-jpa:4.0.7=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.integration:spring-integration-core:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.integration:spring-integration-jdbc:7.0.6=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-aop:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-aspects:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-beans:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-context:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-core:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-expression:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-jdbc:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-messaging:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-orm:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-test:7.0.9=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-tx:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-tx:7.0.9=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-web:7.0.9=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-webmvc:7.0.9=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-database-commons:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-jdbc:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-junit-jupiter:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-postgresql:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-toxiproxy:2.0.5=postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-database-commons:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-jdbc:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-junit-jupiter:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-postgresql:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-toxiproxy:2.0.5=jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers:2.0.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs org.xmlunit:xmlunit-core:2.10.4=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.yaml:snakeyaml:2.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.yaml:snakeyaml:2.5=compileClasspath,jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson.core:jackson-core:3.1.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson.core:jackson-databind:3.1.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson:jackson-bom:3.1.5=jpaPlatformPerformanceTestCompileClasspath,jpaPlatformPerformanceTestRuntimeClasspath,postgresqlIntegrationTestCompileClasspath,postgresqlIntegrationTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath diff --git a/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java b/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java index 72c94470..a1531483 100644 --- a/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java +++ b/src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/JpaModuleBoundaryTest.java @@ -257,9 +257,22 @@ class JpaModuleBoundaryTest { private enum ProductionOnly implements com.tngtech.archunit.core.importer.ImportOption { INSTANCE; + /** The module's own artifacts, minus the ones that are not production. */ + private static final String MODULE_JAR_PREFIX = "/build/libs/persistence-jpa-"; + @Override public boolean includes(Location location) { - return location.contains("/classes/java/main/"); + if (location.contains("/classes/java/main/")) { + return true; + } + // Since this leaf adopted java-test-fixtures (ADR-BUILD-001) its own test classpath carries + // the module as a jar rather than as build/classes/java/main — the fixtures' self-dependency + // on the main component resolves to the jar variant. Matching only the class directory made + // this corpus empty, and the non-vacuity check above is the only reason that surfaced as a + // failure rather than as four rules quietly passing over nothing. + return location.contains(MODULE_JAR_PREFIX) + && !location.contains("-test-fixtures.jar") + && !location.contains("-testkit.jar"); } } diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityExposureCondition.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/EntityMappingCondition.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaArchitectureRules.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/JpaAuditMechanismRule.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/arch/TenantScopedRepositoryPredicate.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/envers/AuditedDocument.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/CommitAmbiguityProxy.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/failure/PostgreSqlFailureScenario.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/FetchPaginationExpectation.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedChild.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/fetch/PagedParent.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/IdentityEntity.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/SequenceEntity.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/id/UuidV7Generator.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingDataSource.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/jdbc/CountingJdbcBatchCounter.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityState.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/EntityStateProbe.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleChild.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/lifecycle/LifecycleParent.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/DurationMillisConverter.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/MappingEntity.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/mapping/Money.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationContractRunner.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationScenario.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/migration/MigrationSnapshot.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/pool/PoolMeasurement.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContainerFactory.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlContractExtension.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/postgresql/PostgreSqlVersion.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/FetchExpectation.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/JpaQueryAssertions.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryExpectation.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/query/QueryMeasurement.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/NormalizedPlan.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/PostgreSqlExplainRunner.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanAssertions.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/queryplan/QueryPlanExpectation.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseGate.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseManifest.java diff --git a/src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java b/src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java similarity index 100% rename from src/adapter/outbound/persistence-jpa/src/testkit/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java rename to src/adapter/outbound/persistence-jpa/src/testFixtures/java/dev/caskeleton/adapter/outbound/persistence/testkit/release/JpaReleaseRendering.java diff --git a/src/adapter/outbound/persistence-mongo/build.gradle b/src/adapter/outbound/persistence-mongo/build.gradle index 80fc55f7..cfbb6be7 100644 --- a/src/adapter/outbound/persistence-mongo/build.gradle +++ b/src/adapter/outbound/persistence-mongo/build.gradle @@ -1,3 +1,7 @@ +// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001. Applied here rather than +// from the root, the way the GraphQL leaf does: only a leaf that has shared test code needs it. +apply plugin: 'java-test-fixtures' + // MongoDB Document Persistence Platform leaf — see // docs/superpowers/specs/2026-08-11-mongodb-document-persistence-platform-design.md (design package) // and docs/mongodb/repository-adaptation.md (how the design's 19 stable + 12 advanced library @@ -30,7 +34,7 @@ dependencies { // The design's module dependency table is enforced as package rules, so ArchUnit is what keeps // "packages instead of modules" from meaning "no boundary at all". - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' + testImplementation libs.archunit.junit5 testImplementation 'io.projectreactor:reactor-test' // Real replica set / failover / migration lanes (design §29). Test-scoped so no production // package can reach a container fixture. @@ -40,32 +44,43 @@ dependencies { testImplementation 'org.testcontainers:testcontainers-toxiproxy' } -// The testkit is its own source set rather than part of `test` because several lanes consume it and -// because the design forbids a production module from depending on the testkit. Declaring its -// dependencies only on the test configurations gives that guarantee without a new Gradle project. -strictTestLanes { - // The testkit compiles against exactly what a test does: `implementation` inheritance runs - // through testImplementation, so this is the module's own dependencies plus the test libraries. - sourceSet('testkit') { compilesAgainst 'main' } - sourceSet('mongoPerformanceTest') { compilesAgainst 'main', 'testkit' } -} - -// Every test lane compiles and runs against the testkit. +// Shared test code lives in src/testFixtures, per ADR-BUILD-001. This leaf is the first migration +// off `ca.testkit-publisher`: one leaf, two lanes, no cross-module consumer — the proof that the +// path works before the published testkits follow. // -// No publishAs: this leaf's testkit is consumed inside the leaf and is not offered to the -// composition root, unlike the JPA one whose ArchUnit rule pack the root applies to the production -// graph. Publishing is opt-in in the convention precisely so that difference stays a decision. -testkitPublisher { - consumedBy 'test' +// `java-test-fixtures` creates the source set and puts it on `test`'s classpath itself, so neither a +// `sourceSet('testFixtures')` declaration nor a `testFixturesPublisher { consumedBy 'test' }` is needed. +// The performance lane is not `test` and still has to say it consumes the fixtures, which is what +// `compilesAgainst` is for — it looks a source set up rather than creating one, so naming a +// plugin-created set works unchanged. +// +// What is given up: the convention's opt-in publishing. This leaf's fixtures are now a consumable +// variant whether or not anybody asks for them. ADR-BUILD-001 records that as an accepted loss — +// unconsumed fixtures are unconsumed, and a leaf that genuinely must not offer them needs a module, +// not a third convention. +strictTestLanes { + sourceSet('mongoPerformanceTest') { compilesAgainst 'main', 'testFixtures' } } +// The fixtures declare what they compile against, rather than inheriting the test configuration. +// +// The `testkit` source set used to extend `testImplementation`, so it silently saw every test +// library this leaf declared. That is convenient and it is also how a fixture acquires a dependency +// nobody chose for it. Listing them here is three more lines and makes the fixtures' own surface +// reviewable — the same model the GraphQL leaf already uses. dependencies { - testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' - testkitImplementation 'io.projectreactor:reactor-test' - testkitImplementation 'org.testcontainers:testcontainers' - testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter' - testkitImplementation 'org.testcontainers:testcontainers-mongodb' - testkitImplementation 'org.testcontainers:testcontainers-toxiproxy' + testFixturesImplementation libs.archunit.junit5 + testFixturesImplementation 'io.projectreactor:reactor-test' + testFixturesImplementation 'org.assertj:assertj-core' + testFixturesImplementation 'org.mongodb:bson' + // The ArchUnit rule pack names Spring Data's Repository marker in a rule, so it needs the + // type on its compile classpath even though it never calls it. + testFixturesImplementation 'org.springframework.data:spring-data-commons' + testFixturesImplementation 'org.testcontainers:testcontainers' + testFixturesImplementation 'org.testcontainers:testcontainers-junit-jupiter' + testFixturesImplementation 'org.testcontainers:testcontainers-mongodb' + testFixturesImplementation 'org.testcontainers:testcontainers-toxiproxy' + testFixturesImplementation libs.toxiproxy.java } // Pinned server images. The design forbids `latest` for a certification lane (Task 44): a mutable diff --git a/src/adapter/outbound/persistence-mongo/gradle.lockfile b/src/adapter/outbound/persistence-mongo/gradle.lockfile index 330081a7..d0f37f07 100644 --- a/src/adapter/outbound/persistence-mongo/gradle.lockfile +++ b/src/adapter/outbound/persistence-mongo/gradle.lockfile @@ -2,82 +2,83 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath -ch.qos.logback:logback-classic:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -ch.qos.logback:logback-core:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.21=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.github.docker-java:docker-java-api:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.docker-java:docker-java-transport-zerodep:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.docker-java:docker-java-transport:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +ch.qos.logback:logback-classic:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +ch.qos.logback:logback-core:1.5.38=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.github.docker-java:docker-java-api:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.docker-java:docker-java-transport-zerodep:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.docker-java:docker-java-transport:3.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs com.github.spotbugs:spotbugs:4.10.2=spotbugs com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs -com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.auto:auto-common:1.2.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.auto:auto-common:1.2.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.code.findbugs:jsr305:3.0.2=checkstyle,spotbugs -com.google.code.gson:gson:2.13.2=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.code.gson:gson:2.13.2=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath -com.google.errorprone:error_prone_annotations:2.41.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.41.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.google.errorprone:error_prone_annotations:2.47.0=checkstyle -com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.guava:guava:33.5.0-jre=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:guava:33.5.0-jre=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.google.guava:guava:33.6.0-jre=checkstyle -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins com.jayway.jsonpath:json-path:2.10.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.puppycrawl.tools:checkstyle:13.5.0=checkstyle -com.tngtech.archunit:archunit-junit5-api:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine:1.3.0=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit-junit5:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -com.tngtech.archunit:archunit:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-api:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=mongoPerformanceTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine:1.3.0=mongoPerformanceTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit-junit5:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +com.tngtech.archunit:archunit:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath com.vaadin.external.google:android-json:0.0.20131108.vaadin1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-beanutils:commons-beanutils:1.11.0=checkstyle -commons-codec:commons-codec:1.19.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-codec:commons-codec:1.19.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle -commons-io:commons-io:2.20.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-io:commons-io:2.20.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath commons-io:commons-io:2.21.0=spotbugs -commons-logging:commons-logging:1.3.6=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +commons-logging:commons-logging:1.3.6=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +eu.rekawek.toxiproxy:toxiproxy-java:2.1.7=testFixturesCompileClasspath,testFixturesRuntimeClasspath info.picocli:picocli:4.7.7=checkstyle -io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -io.micrometer:micrometer-commons:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-core:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.micrometer:micrometer-observation:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor:reactor-core:3.8.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -io.projectreactor:reactor-test:3.8.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +io.micrometer:micrometer-commons:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-core:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.micrometer:micrometer-observation:1.16.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor:reactor-core:3.8.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +io.projectreactor:reactor-test:3.8.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.activation:jakarta.activation-api:2.1.4=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor +javax.inject:javax.inject:1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor jaxen:jaxen:2.0.6=spotbugs net.bytebuddy:byte-buddy-agent:1.17.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.bytebuddy:byte-buddy:1.17.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -net.java.dev.jna:jna:5.18.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +net.java.dev.jna:jna:5.18.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.minidev:accessors-smart:2.6.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.minidev:json-smart:2.6.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs org.antlr:antlr4-runtime:4.13.2=checkstyle org.apache.bcel:bcel:6.12.0=spotbugs -org.apache.commons:commons-compress:1.28.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.apache.commons:commons-lang3:3.20.0=checkstyle,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-compress:1.28.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.commons:commons-lang3:3.20.0=checkstyle,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,spotbugs,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.commons:commons-text:1.15.0=spotbugs org.apache.commons:commons-text:1.3=checkstyle org.apache.httpcomponents:httpclient:4.5.13=checkstyle org.apache.httpcomponents:httpcore:4.4.16=checkstyle -org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.logging.log4j:log4j-core:2.25.5=spotbugs -org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.maven.doxia:doxia-core:1.12.0=checkstyle org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle @@ -87,7 +88,7 @@ org.apache.tomcat.embed:tomcat-embed-el:11.0.24=mongoPerformanceTestCompileClass org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.apache.xbean:xbean-reflect:3.7=checkstyle org.apiguardian:apiguardian-api:1.1.2=mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath -org.assertj:assertj-core:3.27.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.assertj:assertj-core:3.27.7=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.awaitility:awaitility:4.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle @@ -95,29 +96,29 @@ org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle org.codehaus.plexus:plexus-utils:3.3.0=checkstyle org.dom4j:dom4j:2.2.0=spotbugs org.hamcrest:hamcrest:3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.hdrhistogram:HdrHistogram:2.2.2=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle -org.jetbrains:annotations:17.0.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,mongoPerformanceTestAnnotationProcessor,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath +org.jetbrains:annotations:17.0.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,mongoPerformanceTestAnnotationProcessor,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testFixturesAnnotationProcessor,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitAnnotationProcessor,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-api:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-engine:6.0.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter-params:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit.jupiter:junit-jupiter:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-commons:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.junit.platform:junit-platform-engine:6.0.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-commons:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit.platform:junit-platform-engine:6.0.3=mongoPerformanceTestRuntimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.junit.platform:junit-platform-launcher:6.0.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.junit:junit-bom:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.junit:junit-bom:6.0.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs -org.latencyutils:LatencyUtils:2.0.3=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.latencyutils:LatencyUtils:2.0.3=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath org.mockito:mockito-core:5.20.0=mockitoAgent,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.mockito:mockito-junit-jupiter:5.20.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.mongodb:bson-record-codec:5.6.5=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.mongodb:bson:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.mongodb:mongodb-driver-core:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.mongodb:mongodb-driver-reactivestreams:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.mongodb:mongodb-driver-sync:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.mongodb:bson-record-codec:5.6.5=mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath +org.mongodb:bson:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.mongodb:mongodb-driver-core:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.mongodb:mongodb-driver-reactivestreams:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.mongodb:mongodb-driver-sync:5.6.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.objenesis:objenesis:3.3=mongoPerformanceTestRuntimeClasspath,testRuntimeClasspath,testkitRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath org.osgi:org.osgi.resource:1.0.0=compileClasspath,mongoPerformanceTestCompileClasspath,testCompileClasspath,testkitCompileClasspath @@ -128,64 +129,64 @@ org.ow2.asm:asm-tree:9.10.1=spotbugs org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm:9.10.1=spotbugs org.ow2.asm:asm:9.7.1=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.pcollections:pcollections:4.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testkitAnnotationProcessor -org.reactivestreams:reactive-streams:1.0.4=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.pcollections:pcollections:4.0.1=annotationProcessor,mongoPerformanceTestAnnotationProcessor,testAnnotationProcessor,testFixturesAnnotationProcessor,testkitAnnotationProcessor +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.reflections:reflections:0.10.2=checkstyle -org.rnorth.duct-tape:duct-tape:1.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.rnorth.duct-tape:duct-tape:1.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.skyscreamer:jsonassert:1.5.3=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.slf4j:slf4j-api:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.slf4j:slf4j-api:2.0.18=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j org.slf4j:slf4j-simple:2.0.18=checkstyle -org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor -org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-data-commons:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-http-converter:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-jackson:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-reactor:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-reactor:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-resttestclient:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-servlet:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-data-mongodb-reactive:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-data-mongodb-reactive:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-jackson:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter-mongodb:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-tomcat:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-starter-webmvc:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-tomcat:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-web-server:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc-test:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework.boot:spring-boot-webmvc:4.0.8=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.boot:spring-boot:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.data:spring-data-commons:4.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework.data:spring-data-mongodb:5.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-aop:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-beans:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-context:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-core:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-expression:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.boot:spring-boot:4.0.8=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.data:spring-data-commons:4.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework.data:spring-data-mongodb:5.0.7=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-aop:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-beans:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-context:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-core:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-expression:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-test:7.0.9=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.springframework:spring-tx:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.springframework:spring-tx:7.0.9=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-web:7.0.9=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.springframework:spring-webmvc:7.0.9=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-junit-jupiter:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-mongodb:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers-toxiproxy:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.testcontainers:testcontainers:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-junit-jupiter:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-mongodb:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers-toxiproxy:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.testcontainers:testcontainers:2.0.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testFixturesCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs org.xmlunit:xmlunit-core:2.10.4=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath -org.yaml:snakeyaml:2.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath +org.yaml:snakeyaml:2.5=compileClasspath,mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,runtimeClasspath,testCompileClasspath,testFixturesRuntimeClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson.core:jackson-core:3.1.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson.core:jackson-databind:3.1.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath tools.jackson:jackson-bom:3.1.5=mongoPerformanceTestCompileClasspath,mongoPerformanceTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath,testkitCompileClasspath,testkitRuntimeClasspath diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoCollectionProfile.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoOperation.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/architecture/MongoRepositoryArchitectureRules.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAccessRules.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/arch/MongoAdvancedRules.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasCapabilityContractSuite.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/atlas/MongoAtlasLocalContainer.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoStableContractSuite.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionCapabilityReport.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/compat/MongoVersionMatrix.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoFailoverScenario.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoNetworkFaultController.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoPrimaryController.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoProxiedReplicaSetNode.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/MongoThreeNodeReplicaSet.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/failover/ToxiproxyMongoNetworkFaultController.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshot.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoBsonSnapshotAssert.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/mapping/MongoRoundTripContract.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoBackfillRestartFixture.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationContractSuite.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/migration/MongoMigrationSnapshotFixture.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoChaosGate.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoPerformanceGate.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/performance/MongoResourceBudgetReport.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseContract.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidence.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/release/MongoReleaseEvidenceVerifier.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoAuthenticatedReplicaSetContainer.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetContract.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoReplicaSetFixture.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoSingleReplicaSetContainer.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/rs/MongoTlsReplicaSetContainer.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoChunkMigrationController.java diff --git a/src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java b/src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java similarity index 100% rename from src/adapter/outbound/persistence-mongo/src/testkit/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java rename to src/adapter/outbound/persistence-mongo/src/testFixtures/java/dev/caskeleton/adapter/outbound/mongo/testkit/sharded/MongoShardingContractSuite.java diff --git a/src/app-bootstrap/CLAUDE.md b/src/app-bootstrap/CLAUDE.md index 49c2f28c..58cf3f81 100644 --- a/src/app-bootstrap/CLAUDE.md +++ b/src/app-bootstrap/CLAUDE.md @@ -30,6 +30,57 @@ Package root: `dev.caskeleton.bootstrap`. - Concrete business policy. - Controllers, use cases, repository adapters, or mappers authored directly under bootstrap. - Settings classes that implement business logic. +- Implementing an `application-core` or `domain-core` port. A class that implements one is an + outbound adapter and belongs in an `adapter/outbound/*` leaf, however its dependencies are + configured — `CompositionRootBoundaryArchitectureTest` enforces this and holds the one documented + exception. +- Raising a startup failure with `throw new IllegalStateException(...)`. Use `StartupFailures` + (`envValidation` / `profileMismatch` / `requiredAdapterDisabled` / `migrationFailed`), so the + failure carries its sysexits exit code and structured log fields — + `StartupFailureContractArchitectureTest` enforces this. +- Answering "is this production?" locally. `dev.caskeleton.shared.runtime.RuntimeEnvironment` owns + the profile vocabulary for the whole repository. +- Depending on `org.testcontainers` from `src/test`. Real-service tests belong to + `src/integrationTest`; see `docs/testing/TESTING_STRATEGY.md`. + +## Package layout + +The rules below exist because their absence is what produced the drift this module was reviewed for +(`docs/reviews/2026-09-07-app-bootstrap-module-code-review.md`, BOOT-006 / BOOT-007). Half the +capabilities lived under `autoconfigure/` and half at the package root; `Settings` classes were split +between a `settings/` package and their feature packages with no stated rule; and the same +decomposition problem was solved two different ways in the same leaf — Notification as nine focused +configurations, Redis as one 376-line class. + +- **A capability's assembly lives in `autoconfigure//`.** Everything a deployment can + switch on is a capability: `fileserver`, `httpclient`, `jpa`, `redis`, `notification`, `mongo`, + `outbox`, `tracing`, `idempotency`, `lock`, `messaging`. +- **`Settings` lives beside its capability**, not in `settings/`. The `settings/` package + holds only settings that belong to no capability — `BootstrapSettings`, `LoggingSettings`, + `PrivacySettings`, `RuntimeSafetySettings`. +- **`runtime/` holds guards that run whatever is switched on.** Subject matter does not decide + ownership: `JpaSchemaSafetyValidator` reads JPA properties but runs when JPA is off, which is the + whole point of it, so it is a runtime guard and not part of the JPA capability. Moving guards like + it into `autoconfigure/jpa/` made `FiveAdapterOffInventoryTest` fail — correctly, because that test + asserts a switched-off capability owns no beans, and the package is how it classifies them. +- **`runtime/startup/` holds the startup failure vocabulary** — phases, error codes, exceptions, the + `StartupFailures` factory, `StartupValidationContext`, and `StartupOrder`. + +## Decomposition threshold + +State it rather than leaving it to taste, because "it felt long enough" is what produced two +different answers for the same question. + +- One `@Configuration` holds one assembly concern. Split when it passes roughly **eight `@Bean` + methods**, or as soon as it assembles two unrelated port families — whichever comes first. +- One `@ConfigurationProperties` class owns one capability namespace. +- A capability that needs more than one configuration gets a root class holding the master switch, + `@EnableConfigurationProperties`, and an `@Import` of the per-concern classes. Scan nothing: + `@Import` from a gated root is what keeps the master switch load-bearing rather than advisory. + `autoconfigure/redis/RedisCapabilityConfig` and `notification/NotificationRootAutoConfiguration` + are the two worked examples. +- Shared helpers of a split capability get their own type named for what they are, not a `*Util` + bucket — `autoconfigure/redis/RedisKeys` is the reference. ## Test diff --git a/src/app-bootstrap/build.gradle b/src/app-bootstrap/build.gradle index 6d70ffb7..046c3d5b 100644 --- a/src/app-bootstrap/build.gradle +++ b/src/app-bootstrap/build.gradle @@ -22,6 +22,23 @@ configurations { } } +// The infrastructure lane. Testcontainers-backed tests boot a real PostgreSQL, so they belong to a +// source set of their own rather than to `test`: the ordinary focused command for this leaf — +// `./gradlew :app-bootstrap:test` — must not require a Docker daemon, and a leaf whose fastest +// feedback loop starts a database is a leaf nobody runs the fast loop on. This is the same shape +// persistence-jpa, web, websocket and objectstorage already use, declared through the repository's +// own strictTestLanes DSL rather than by hand. +strictTestLanes { + sourceSet('integrationTest') { + compilesAgainst 'main' + inherits 'implementation', 'compileOnly', 'runtimeOnly', 'annotationProcessor' + } + lane('integrationTest') { + description = 'Testcontainers-backed real-PostgreSQL integration contracts.' + sourceSet = 'integrationTest' + } +} + sourceSets { sampleOffTest { java.srcDirs = sourceSets.test.java.srcDirs @@ -59,8 +76,8 @@ dependencies { // The JPA rule pack, for the production architecture suite. Test scope only: the rules are // ArchUnit assertions, and the composition root is the one place that can see every runtime // leaf at once — which is what makes it the right consumer and the wrong shipper. - testImplementation(project(path: ':adapter:outbound:persistence-jpa', configuration: 'jpaTestkit')) - testImplementation(project(path: ':adapter:inbound:web', configuration: 'webTestkit')) + testImplementation(testFixtures(project(':adapter:outbound:persistence-jpa'))) + testImplementation(testFixtures(project(':adapter:inbound:web'))) // Shipped so an operator can enable it with one environment variable. An adapter that is // absent is not the same contract as one that is off: absence cannot be reversed at deploy time, // and it hides every gating defect, because code that is not there holds no beans whatever its @@ -100,7 +117,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-validation' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' - implementation 'me.paulschwarz:spring-dotenv:4.0.0' + implementation libs.spring.dotenv // Boot 4 Flyway API/autoconfiguration: the composition root drives startup migration // (MigrationStartupConfig). See README. implementation 'org.springframework.boot:spring-boot-flyway' @@ -128,9 +145,17 @@ dependencies { // test-only: @WithMockUser for the actuator security authorization tests. See README. testImplementation 'org.springframework.security:spring-security-test' - // test-only: Testcontainers PostgreSQL for the outbox contract tests. - testImplementation 'org.testcontainers:testcontainers-postgresql' - testImplementation 'org.testcontainers:testcontainers-junit-jupiter' + // integration-lane-only: Testcontainers PostgreSQL. Declared here and NOT on testImplementation + // so `:app-bootstrap:test` cannot start a container — the source-set boundary is what enforces + // BOOT-014, and a dependency left on the test classpath would let the boundary be crossed back + // by a single import. + integrationTestImplementation 'org.testcontainers:testcontainers-postgresql' + integrationTestImplementation 'org.testcontainers:testcontainers-junit-jupiter' + // test-only, compile-only: TestcontainersUsingFixture declares a PostgreSQLContainer *field type* + // as the non-vacuity positive control for the Testcontainers-ban ArchUnit rule. ClassFileImporter + // reads its bytecode without resolving or constructing the field, so the type is never needed at + // runtime — the same compileOnly shape the other violation fixtures use. + testCompileOnly 'org.testcontainers:testcontainers-postgresql' // test-only: Boot 4 split JPA slice annotations into dedicated test modules. testImplementation 'org.springframework.boot:spring-boot-data-jpa-test' // test-only: PG JDBC driver (the vendor module's runtimeOnly does not leak here). See README. @@ -139,10 +164,11 @@ dependencies { testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa' testImplementation 'com.zaxxer:HikariCP' - // test-only: JdbcLockRegistry for the distributed-lock contract test (two simulated instances). See README. - testImplementation 'org.springframework.integration:spring-integration-jdbc' + // integration-lane-only: JdbcLockRegistry for the distributed-lock contract test (two simulated + // instances). Nothing under src/test imports org.springframework.integration. + integrationTestImplementation 'org.springframework.integration:spring-integration-jdbc' - testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' + testImplementation libs.archunit.junit5 // test-only: jackson-databind for the deserialization-policy boundary test. See README. testImplementation 'org.springframework.boot:spring-boot-starter-json' // test-only: parses checked-in Messaging capability registries for the fail-closed drift gate. @@ -165,7 +191,7 @@ dependencies { implementation 'org.apache.kafka:kafka-clients' // org.apache.kafka.. testCompileOnly 'jakarta.ws.rs:jakarta.ws.rs-api' // jakarta.ws.rs.. // test-only: @RefreshScope for the no-refresh-scope violation fixture (version pinned; not in the BOM). See README. - testCompileOnly 'org.springframework.cloud:spring-cloud-context:4.1.4' // org.springframework.cloud.context.. + testCompileOnly libs.spring.cloud.context // org.springframework.cloud.context.. // analysis-only: SpotBugs resolves the full type hierarchy of everything on the auxiliary // classpath, and spring-beans/spring-context/spring-web each ship an optional Groovy integration // class (GroovyBeanDefinitionReader, GenericGroovyApplicationContext, GroovyWebApplicationContext). @@ -179,11 +205,11 @@ dependencies { testCompileOnly 'org.apache.groovy:groovy' // groovy.lang.GroovyObject // JSON log encoder; implementation (not runtimeOnly) because StartupFailures uses StructuredArguments at compile time. See README. - implementation 'net.logstash.logback:logstash-logback-encoder:8.0' + implementation libs.logstash.logback.encoder // test-only: ApprovalTests JSON snapshot verification for the contract-verification suite // (feature-contract-verification-test-suite D7 — envelope/error shape snapshots). See README. - testImplementation 'com.approvaltests:approvaltests:31.0.0' + testImplementation libs.approvaltests // test-only: JUnit Platform Test Kit — proves optional-adapter tests report SKIPPED (never FAILED) // when their enable-flag env var is unset (feature-contract-verification-test-suite D3, Claims #7). See README. testImplementation 'org.junit.platform:junit-platform-testkit' diff --git a/src/app-bootstrap/gradle.lockfile b/src/app-bootstrap/gradle.lockfile index aebfadb4..655a2ba3 100644 --- a/src/app-bootstrap/gradle.lockfile +++ b/src/app-bootstrap/gradle.lockfile @@ -1,115 +1,116 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -aopalliance:aopalliance:1.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -at.yawk.lz4:lz4-java:1.10.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +aopalliance:aopalliance:1.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +at.yawk.lz4:lz4-java:1.10.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +biz.aQute.bnd:biz.aQute.bnd.annotation:7.1.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath ch.qos.logback:logback-classic:1.5.34=sampleFixture -ch.qos.logback:logback-classic:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-classic:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath ch.qos.logback:logback-core:1.5.34=sampleFixture -ch.qos.logback:logback-core:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.approvaltests:approvaltests-util:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.approvaltests:approvaltests:31.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.ethlo.time:itu:1.14.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.5.38=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.approvaltests:approvaltests-util:31.0.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.approvaltests:approvaltests:31.0.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.ethlo.time:itu:1.14.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.fasterxml.jackson.core:jackson-core:2.21.4=sampleFixture -com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-core:2.21.5=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.fasterxml.jackson.core:jackson-databind:2.21.4=sampleFixture -com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson.core:jackson-databind:2.21.5=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.fasterxml.jackson.dataformat:jackson-dataformat-toml:2.21.4=sampleFixture com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.4=sampleFixture -com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.21.4=sampleFixture com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.4=sampleFixture -com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.fasterxml.jackson.module:jackson-module-parameter-names:2.21.4=sampleFixture com.fasterxml.jackson:jackson-bom:2.21.4=sampleFixture -com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.fasterxml:classmate:1.7.3=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.github.docker-java:docker-java-api:3.7.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.docker-java:docker-java-transport-zerodep:3.7.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.github.docker-java:docker-java-transport:3.7.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml.jackson:jackson-bom:2.21.5=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.fasterxml:classmate:1.7.3=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.ben-manes.caffeine:caffeine:3.2.4=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.github.docker-java:docker-java-api:3.7.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-transport-zerodep:3.7.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.github.docker-java:docker-java-transport:3.7.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.github.f4b6a3:uuid-creator:6.1.1=sampleFixture,testRuntimeClasspath -com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.github.luben:zstd-jni:1.5.6-10=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.github.luben:zstd-jni:1.5.6-10=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.github.spotbugs:spotbugs-annotations:4.10.2=spotbugs com.github.spotbugs:spotbugs:4.10.2=spotbugs -com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.github.stephenc.jcip:jcip-annotations:1.0-1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath com.google.android:annotations:4.1.1.4=conditionalTransportTestRuntimeClasspath com.google.api.grpc:proto-google-common-protos:2.41.0=conditionalTransportTestRuntimeClasspath -com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.google.auto:auto-common:1.2.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.auto.value:auto-value-annotations:1.9=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.auto:auto-common:1.2.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor com.google.code.findbugs:jsr305:3.0.2=checkstyle,conditionalTransportTestRuntimeClasspath,spotbugs -com.google.code.gson:gson:2.13.2=conditionalTransportTestRuntimeClasspath,spotbugs -com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath -com.google.errorprone:error_prone_annotations:2.41.0=conditionalTransportTestRuntimeClasspath,spotbugs +com.google.code.gson:gson:2.13.2=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,spotbugs,testRuntimeClasspath +com.google.errorprone:error_prone_annotation:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.38.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +com.google.errorprone:error_prone_annotations:2.41.0=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,spotbugs,testRuntimeClasspath com.google.errorprone:error_prone_annotations:2.47.0=checkstyle -com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_annotations:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_check_api:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.errorprone:error_prone_core:2.49.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.googlejavaformat:google-java-format:1.35.0=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor com.google.guava:failureaccess:1.0.2=conditionalTransportTestRuntimeClasspath -com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor com.google.guava:guava:33.2.1-jre=conditionalTransportTestRuntimeClasspath -com.google.guava:guava:33.5.0-jre=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:guava:33.5.0-jre=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor com.google.guava:guava:33.6.0-jre=checkstyle -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor com.google.j2objc:j2objc-annotations:2.8=conditionalTransportTestRuntimeClasspath -com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,checkstyle,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor com.google.protobuf:protobuf-java-util:3.25.5=conditionalTransportTestRuntimeClasspath com.google.protobuf:protobuf-java:3.25.5=conditionalTransportTestRuntimeClasspath -com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -com.graphql-java:graphql-java:25.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.graphql-java:java-dataloader:6.0.0=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.h2database:h2:2.4.240=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +com.graphql-java:graphql-java:25.0=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.graphql-java:java-dataloader:6.0.0=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.h2database:h2:2.4.240=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.h3xstream.findsecbugs:findsecbugs-plugin:1.14.0=spotbugsPlugins -com.jayway.jsonpath:json-path:2.10.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.networknt:json-schema-validator:3.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.nimbusds:content-type:2.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.nimbusds:lang-tag:1.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.jayway.jsonpath:json-path:2.10.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.networknt:json-schema-validator:3.0.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.nimbusds:content-type:2.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.nimbusds:lang-tag:1.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.nimbusds:nimbus-jose-jwt:10.4=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.nimbusds:nimbus-jose-jwt:9.37.4=sampleFixture -com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.nimbusds:oauth2-oidc-sdk:11.26.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.puppycrawl.tools:checkstyle:13.5.0=checkstyle -com.rabbitmq:amqp-client:5.27.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.squareup.okhttp3:okhttp-jvm:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.rabbitmq:amqp-client:5.27.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp-jvm:5.2.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.squareup.okhttp3:okhttp:4.12.0=sampleFixture -com.squareup.okhttp3:okhttp:5.2.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.squareup.okio:okio-jvm:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:5.2.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.squareup.okio:okio-jvm:3.16.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.squareup.okio:okio-jvm:3.6.0=sampleFixture -com.squareup.okio:okio:3.16.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.squareup.okio:okio:3.16.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath com.squareup.okio:okio:3.6.0=sampleFixture -com.sun.istack:istack-commons-runtime:4.1.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.tngtech.archunit:archunit-junit5-api:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.tngtech.archunit:archunit-junit5-engine:1.3.0=sampleOffTestRuntimeClasspath,testRuntimeClasspath -com.tngtech.archunit:archunit-junit5:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.tngtech.archunit:archunit:1.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -com.vaadin.external.google:android-json:0.0.20131108.vaadin1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.sun.istack:istack-commons-runtime:4.1.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.tngtech.archunit:archunit-junit5-api:1.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine-api:1.3.0=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.tngtech.archunit:archunit-junit5-engine:1.3.0=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +com.tngtech.archunit:archunit-junit5:1.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.tngtech.archunit:archunit:1.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.vaadin.external.google:android-json:0.0.20131108.vaadin1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath com.zaxxer:HikariCP:6.3.3=sampleFixture -com.zaxxer:HikariCP:7.0.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +com.zaxxer:HikariCP:7.0.2=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath commons-beanutils:commons-beanutils:1.11.0=checkstyle -commons-codec:commons-codec:1.19.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-codec:commons-codec:1.19.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath commons-collections:commons-collections:3.2.2=checkstyle -commons-io:commons-io:2.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath commons-io:commons-io:2.21.0=spotbugs -commons-logging:commons-logging:1.3.6=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +commons-logging:commons-logging:1.3.6=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +eu.rekawek.toxiproxy:toxiproxy-java:2.1.11=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath info.picocli:picocli:4.7.7=checkstyle -io.cloudevents:cloudevents-api:4.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.cloudevents:cloudevents-core:4.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.cdimascio:dotenv-java:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor -io.github.resilience4j:resilience4j-bulkhead:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.resilience4j:resilience4j-core:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.resilience4j:resilience4j-micrometer:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.resilience4j:resilience4j-ratelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.resilience4j:resilience4j-retry:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.github.resilience4j:resilience4j-timelimiter:2.2.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.cloudevents:cloudevents-api:4.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.cloudevents:cloudevents-core:4.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.cdimascio:dotenv-java:3.0.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +io.github.resilience4j:resilience4j-bulkhead:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.resilience4j:resilience4j-circuitbreaker:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.resilience4j:resilience4j-core:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.resilience4j:resilience4j-micrometer:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.resilience4j:resilience4j-ratelimiter:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.resilience4j:resilience4j-retry:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.github.resilience4j:resilience4j-timelimiter:2.2.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.grpc:grpc-api:1.68.1=conditionalTransportTestRuntimeClasspath io.grpc:grpc-context:1.68.1=conditionalTransportTestRuntimeClasspath io.grpc:grpc-core:1.68.1=conditionalTransportTestRuntimeClasspath @@ -119,399 +120,403 @@ io.grpc:grpc-protobuf:1.68.1=conditionalTransportTestRuntimeClasspath io.grpc:grpc-services:1.68.1=conditionalTransportTestRuntimeClasspath io.grpc:grpc-stub:1.68.1=conditionalTransportTestRuntimeClasspath io.grpc:grpc-util:1.68.1=conditionalTransportTestRuntimeClasspath -io.lettuce:lettuce-core:6.8.2.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.lettuce:lettuce-core:6.8.2.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.micrometer:context-propagation:1.1.4=sampleFixture -io.micrometer:context-propagation:1.2.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:context-propagation:1.2.1=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-commons:1.15.12=sampleFixture -io.micrometer:micrometer-commons:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-commons:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-core:1.15.12=sampleFixture -io.micrometer:micrometer-core:1.16.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-core:1.16.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-jakarta9:1.15.12=sampleFixture -io.micrometer:micrometer-jakarta9:1.16.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-jakarta9:1.16.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-observation:1.15.12=sampleFixture -io.micrometer:micrometer-observation:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-observation:1.16.7=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-registry-prometheus:1.15.12=sampleFixture -io.micrometer:micrometer-registry-prometheus:1.16.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-registry-prometheus:1.16.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-tracing-bridge-otel:1.5.12=sampleFixture -io.micrometer:micrometer-tracing-bridge-otel:1.6.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.micrometer:micrometer-tracing-bridge-otel:1.6.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.micrometer:micrometer-tracing:1.5.12=sampleFixture -io.micrometer:micrometer-tracing:1.6.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -io.netty:netty-buffer:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-base:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-classes-quic:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-compression:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-dns:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-http2:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-http3:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-http:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-marshalling:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-native-quic:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-protobuf:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec-socks:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-codec:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-handler-proxy:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-handler:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-resolver-dns-native-macos:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-resolver-dns:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-resolver:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-transport-classes-epoll:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-transport-native-epoll:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-transport-native-unix-common:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.netty:netty-transport:4.2.17.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.micrometer:micrometer-tracing:1.6.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.netty:netty-buffer:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-base:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-classes-quic:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-compression:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-dns:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-http2:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-http3:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-http:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-marshalling:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-native-quic:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-protobuf:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec-socks:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-codec:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-common:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-handler-proxy:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-handler:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-resolver-dns-classes-macos:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-resolver-dns-native-macos:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-resolver-dns:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-resolver:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-transport-classes-epoll:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-transport-native-epoll:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-transport-native-unix-common:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.netty:netty-transport:4.2.17.Final=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.opentelemetry.semconv:opentelemetry-semconv:1.32.0=sampleFixture -io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry.semconv:opentelemetry-semconv:1.37.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-api:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-api:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-common:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-context:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-context:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-exporter-common:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-exporter-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-exporter-common:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-exporter-otlp-common:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-exporter-otlp-common:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-exporter-otlp:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-exporter-otlp:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-exporter-sender-okhttp:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-extension-trace-propagators:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-extension-trace-propagators:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-sdk-common:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-sdk-common:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.55.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-sdk-logs:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-sdk-logs:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-sdk-metrics:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-sdk-metrics:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-sdk-trace:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-sdk-trace:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.opentelemetry:opentelemetry-sdk:1.49.0=sampleFixture -io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.opentelemetry:opentelemetry-sdk:1.55.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.perfmark:perfmark-api:0.27.0=conditionalTransportTestRuntimeClasspath -io.projectreactor.netty:reactor-netty-core:1.3.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -io.projectreactor.netty:reactor-netty-http:1.3.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.projectreactor.netty:reactor-netty-core:1.3.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.projectreactor.netty:reactor-netty-http:1.3.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.projectreactor:reactor-core:3.7.19=sampleFixture -io.projectreactor:reactor-core:3.8.7=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.projectreactor:reactor-core:3.8.7=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.prometheus:prometheus-metrics-config:1.3.10=sampleFixture -io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.prometheus:prometheus-metrics-config:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.prometheus:prometheus-metrics-core:1.3.10=sampleFixture -io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.prometheus:prometheus-metrics-core:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.prometheus:prometheus-metrics-exposition-formats:1.3.10=sampleFixture -io.prometheus:prometheus-metrics-exposition-formats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.prometheus:prometheus-metrics-exposition-formats:1.4.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.prometheus:prometheus-metrics-exposition-textformats:1.3.10=sampleFixture -io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.prometheus:prometheus-metrics-exposition-textformats:1.4.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.prometheus:prometheus-metrics-model:1.3.10=sampleFixture -io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.prometheus:prometheus-metrics-model:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.prometheus:prometheus-metrics-tracer-common:1.3.10=sampleFixture -io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.prometheus:prometheus-metrics-tracer-common:1.4.3=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath io.smallrye:jandex:3.2.0=sampleFixture +io.smallrye:jandex:3.3.2=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.swagger.core.v3:swagger-annotations-jakarta:2.2.29=sampleFixture -io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.swagger.core.v3:swagger-annotations-jakarta:2.2.38=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.swagger.core.v3:swagger-core-jakarta:2.2.29=sampleFixture -io.swagger.core.v3:swagger-core-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +io.swagger.core.v3:swagger-core-jakarta:2.2.38=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath io.swagger.core.v3:swagger-models-jakarta:2.2.29=sampleFixture -io.swagger.core.v3:swagger-models-jakarta:2.2.38=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -jakarta.activation:jakarta.activation-api:2.1.4=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +io.swagger.core.v3:swagger-models-jakarta:2.2.38=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +jakarta.activation:jakarta.activation-api:2.1.4=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.annotation:jakarta.annotation-api:2.1.1=sampleFixture -jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -jakarta.inject:jakarta.inject-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -jakarta.mail:jakarta.mail-api:2.1.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +jakarta.annotation:jakarta.annotation-api:3.0.0=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.inject:jakarta.inject-api:2.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +jakarta.mail:jakarta.mail-api:2.1.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath jakarta.persistence:jakarta.persistence-api:3.1.0=sampleFixture -jakarta.persistence:jakarta.persistence-api:3.2.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -jakarta.transaction:jakarta.transaction-api:2.0.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.persistence:jakarta.persistence-api:3.2.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.servlet:jakarta.servlet-api:6.1.0=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +jakarta.transaction:jakarta.transaction-api:2.0.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath jakarta.validation:jakarta.validation-api:3.0.2=sampleFixture -jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -jakarta.websocket:jakarta.websocket-api:2.2.0=sampleOffTestCompileClasspath,testCompileClasspath -jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=sampleOffTestCompileClasspath,testCompileClasspath -jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -javax.inject:javax.inject:1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +jakarta.validation:jakarta.validation-api:3.1.1=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +jakarta.websocket:jakarta.websocket-api:2.2.0=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +jakarta.ws.rs:jakarta.ws.rs-api:4.0.0=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +javax.inject:javax.inject:1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor jaxen:jaxen:2.0.6=spotbugs -me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.17.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.bytebuddy:byte-buddy:1.17.8=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.java.dev.jna:jna:5.18.1=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.minidev:accessors-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -net.minidev:json-smart:2.6.0=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +me.paulschwarz:spring-dotenv:4.0.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.17.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.17.8=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna:5.18.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.logstash.logback:logstash-logback-encoder:8.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.minidev:accessors-smart:2.6.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +net.minidev:json-smart:2.6.0=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath net.sf.saxon:Saxon-HE:12.9=checkstyle,spotbugs -ognl:ognl:3.3.4=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.antlr:antlr4-runtime:4.13.2=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +ognl:ognl:3.3.4=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.antlr:antlr4-runtime:4.13.2=checkstyle,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.bcel:bcel:6.12.0=spotbugs -org.apache.commons:commons-compress:1.28.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.apache.commons:commons-lang3:3.20.0=checkstyle,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-compress:1.28.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.commons:commons-lang3:3.20.0=checkstyle,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath org.apache.commons:commons-text:1.15.0=spotbugs org.apache.commons:commons-text:1.3=checkstyle -org.apache.groovy:groovy-bom:5.0.8=sampleOffTestCompileClasspath,testCompileClasspath -org.apache.groovy:groovy:5.0.8=sampleOffTestCompileClasspath,testCompileClasspath -org.apache.httpcomponents.client5:httpclient5:5.5.2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.apache.httpcomponents.core5:httpcore5:5.3.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.apache.groovy:groovy-bom:5.0.8=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.apache.groovy:groovy:5.0.8=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.apache.httpcomponents.client5:httpclient5:5.5.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5-h2:5.3.6=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.apache.httpcomponents.core5:httpcore5:5.3.6=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.apache.httpcomponents:httpclient:4.5.13=checkstyle,testRuntimeClasspath org.apache.httpcomponents:httpcore:4.4.16=checkstyle,testRuntimeClasspath -org.apache.kafka:kafka-clients:4.1.2=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.kafka:kafka-clients:4.1.2=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.logging.log4j:log4j-api:2.24.3=sampleFixture -org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-api:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath org.apache.logging.log4j:log4j-core:2.25.5=spotbugs org.apache.logging.log4j:log4j-to-slf4j:2.24.3=sampleFixture -org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.logging.log4j:log4j-to-slf4j:2.25.5=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.maven.doxia:doxia-core:1.12.0=checkstyle org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle org.apache.tomcat.embed:tomcat-embed-core:10.1.55=sampleFixture -org.apache.tomcat.embed:tomcat-embed-core:11.0.24=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-core:11.0.24=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.tomcat.embed:tomcat-embed-el:10.1.55=sampleFixture -org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-el:11.0.24=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.tomcat.embed:tomcat-embed-websocket:10.1.55=sampleFixture -org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apache.tomcat.embed:tomcat-embed-websocket:11.0.24=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.apache.xbean:xbean-reflect:3.7=checkstyle -org.apiguardian:apiguardian-api:1.1.2=conditionalTransportTestCompileClasspath,functionalTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath -org.aspectj:aspectjweaver:1.9.25.1=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.assertj:assertj-core:3.27.7=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.attoparser:attoparser:2.0.7.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.awaitility:awaitility:4.3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.apiguardian:apiguardian-api:1.1.2=conditionalTransportTestCompileClasspath,functionalTestCompileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.aspectj:aspectjweaver:1.9.25.1=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.assertj:assertj-core:3.27.7=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.attoparser:attoparser:2.0.7.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.awaitility:awaitility:4.3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.checkerframework:checker-qual:3.42.0=conditionalTransportTestRuntimeClasspath -org.checkerframework:checker-qual:3.55.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.checkerframework:checker-qual:3.55.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.codehaus.mojo:animal-sniffer-annotations:1.24=conditionalTransportTestRuntimeClasspath org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle org.codehaus.plexus:plexus-utils:3.3.0=checkstyle org.dom4j:dom4j:2.2.0=spotbugs -org.eclipse.angus:angus-activation:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.angus:angus-mail:2.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty.compression:jetty-compression-common:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-alpn-client:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-client:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-http:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-io:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.eclipse.jetty:jetty-util:12.1.12=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.flywaydb:flyway-core:11.14.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.eclipse.angus:angus-activation:2.0.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.angus:angus-mail:2.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty.compression:jetty-compression-common:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty.compression:jetty-compression-gzip:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-alpn-client:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-client:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-http:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-io:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.eclipse.jetty:jetty-util:12.1.12=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.flywaydb:flyway-core:11.14.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.flywaydb:flyway-core:11.7.2=sampleFixture -org.flywaydb:flyway-database-postgresql:11.14.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.flywaydb:flyway-database-postgresql:11.14.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.flywaydb:flyway-database-postgresql:11.7.2=sampleFixture -org.glassfish.jaxb:jaxb-core:4.0.9=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.glassfish.jaxb:jaxb-runtime:4.0.9=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.glassfish.jaxb:txw2:4.0.9=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.hamcrest:hamcrest:3.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.hdrhistogram:HdrHistogram:2.2.2=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.glassfish.jaxb:jaxb-core:4.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.glassfish.jaxb:jaxb-runtime:4.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.glassfish.jaxb:txw2:4.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.hamcrest:hamcrest:3.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hdrhistogram:HdrHistogram:2.2.2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.hibernate.common:hibernate-commons-annotations:7.0.3.Final=sampleFixture -org.hibernate.models:hibernate-models:1.0.1=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.hibernate.models:hibernate-models:1.0.1=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.hibernate.orm:hibernate-core:6.6.53.Final=sampleFixture -org.hibernate.orm:hibernate-core:7.2.24.Final=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hibernate.orm:hibernate-core:7.2.24.Final=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hibernate.orm:hibernate-envers:7.2.24.Final=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.hibernate.validator:hibernate-validator:8.0.3.Final=sampleFixture -org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.hibernate.validator:hibernate-validator:9.0.1.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.javassist:javassist:3.28.0-GA=checkstyle -org.javassist:javassist:3.29.0-GA=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.javassist:javassist:3.29.0-GA=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.jboss.logging:jboss-logging:3.6.3.Final=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-common:1.9.25=sampleFixture org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.25=sampleFixture org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.25=sampleFixture org.jetbrains.kotlin:kotlin-stdlib:1.9.25=sampleFixture -org.jetbrains.kotlin:kotlin-stdlib:2.2.21=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:2.2.21=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.jetbrains:annotations:13.0=productionRuntimeClasspath,runtimeClasspath,sampleFixture -org.jetbrains:annotations:17.0.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,conditionalTransportTestAnnotationProcessor,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,functionalTestCompileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-api:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter-params:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.jupiter:junit-jupiter:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-commons:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-launcher:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit.platform:junit-platform-testkit:6.0.3=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.junit:junit-bom:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jetbrains:annotations:17.0.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.jspecify:jspecify:1.0.1=annotationProcessor,checkstyle,compileClasspath,conditionalTransportTestAnnotationProcessor,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestAnnotationProcessor,functionalTestCompileClasspath,integrationTestAnnotationProcessor,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestAnnotationProcessor,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:6.0.3=conditionalTransportTestRuntimeClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-testkit:6.0.3=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.junit:junit-bom:6.0.3=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.junit:junit-bom:6.1.0=spotbugs -org.latencyutils:LatencyUtils:2.0.3=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.mockito:mockito-core:5.20.0=mockitoAgent,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.mockito:mockito-junit-jupiter:5.20.0=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.mongodb:bson-record-codec:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.mongodb:bson:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.mongodb:mongodb-driver-core:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.mongodb:mongodb-driver-sync:5.6.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.objenesis:objenesis:3.3=sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.openapitools:jackson-databind-nullable:0.2.6=productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.opentest4j:opentest4j:1.3.0=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath -org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath -org.osgi:org.osgi.resource:1.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath -org.osgi:org.osgi.service.serviceloader:1.0.0=compileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.latencyutils:LatencyUtils:2.0.3=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.mockito:mockito-core:5.20.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,mockitoAgent,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-junit-jupiter:5.20.0=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.mongodb:bson-record-codec:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.mongodb:bson:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.mongodb:mongodb-driver-core:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.mongodb:mongodb-driver-sync:5.6.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.objenesis:objenesis:3.3=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.openapitools:jackson-databind-nullable:0.2.6=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,functionalTestCompileClasspath,functionalTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.osgi:org.osgi.annotation.bundle:2.0.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.osgi:org.osgi.annotation.versioning:1.1.2=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.osgi:org.osgi.resource:1.0.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.osgi:org.osgi.service.serviceloader:1.0.0=compileClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath org.ow2.asm:asm-analysis:9.10.1=spotbugs org.ow2.asm:asm-commons:9.10.1=spotbugs org.ow2.asm:asm-tree:9.10.1=spotbugs org.ow2.asm:asm-util:9.10.1=spotbugs org.ow2.asm:asm:9.10.1=spotbugs -org.ow2.asm:asm:9.7.1=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.pcollections:pcollections:4.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor +org.ow2.asm:asm:9.7.1=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.pcollections:pcollections:4.0.1=annotationProcessor,conditionalTransportTestAnnotationProcessor,functionalTestAnnotationProcessor,integrationTestAnnotationProcessor,sampleOffTestAnnotationProcessor,testAnnotationProcessor org.postgresql:postgresql:42.7.11=sampleFixture -org.postgresql:postgresql:42.7.13=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.reactivestreams:reactive-streams:1.0.4=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.postgresql:postgresql:42.7.13=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.reactivestreams:reactive-streams:1.0.4=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.reflections:reflections:0.10.2=checkstyle -org.rnorth.duct-tape:duct-tape:1.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.skyscreamer:jsonassert:1.5.3=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.slf4j:slf4j-api:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath +org.rnorth.duct-tape:duct-tape:1.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.skyscreamer:jsonassert:1.5.3=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.18=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleFixture,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,spotbugs,spotbugsSlf4j,testCompileClasspath,testRuntimeClasspath org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j org.slf4j:slf4j-simple:2.0.18=checkstyle org.springdoc:springdoc-openapi-starter-common:2.8.6=sampleFixture -org.springdoc:springdoc-openapi-starter-common:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springdoc:springdoc-openapi-starter-common:3.0.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.6=sampleFixture -org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.amqp:spring-amqp:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.amqp:spring-rabbit:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.amqp:spring-amqp:4.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.amqp:spring-rabbit:4.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-actuator-autoconfigure:3.5.16=sampleFixture -org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator-autoconfigure:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-actuator:3.5.16=sampleFixture -org.springframework.boot:spring-boot-actuator:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-actuator:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-autoconfigure:3.5.16=sampleFixture -org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-autoconfigure:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-configuration-processor:4.0.8=annotationProcessor -org.springframework.boot:spring-boot-data-commons:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-data-jpa-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-data-jpa:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-data-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-health:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-hibernate:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-http-converter:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-jdbc-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-jpa-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-jpa:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-mail:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-micrometer-metrics:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-micrometer-observation:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-reactor:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-resttestclient:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-servlet:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-data-commons:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-data-jpa-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-data-jpa:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-data-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-flyway:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-health:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-hibernate:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-http-converter:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-jdbc-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-jdbc:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-jpa-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-jpa:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-mail:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-micrometer-metrics:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-micrometer-observation:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-persistence:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-reactor:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-resttestclient:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-security-oauth2-resource-server:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-security:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-servlet:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-sql:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-actuator:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-actuator:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-actuator:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-data-jpa:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-flyway:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-data-jpa:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-data-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-flyway:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-graphql:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-jackson-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-jackson:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-jdbc:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-jdbc:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-jdbc:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-json:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-json:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-json:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-logging:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-mail:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-mongodb:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-logging:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-mail:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-micrometer-metrics:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-mongodb:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-oauth2-resource-server:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-oauth2-resource-server:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-security:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-security:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat-runtime:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-tomcat:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-validation:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-web:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter-web:4.0.8=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-starter-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-web:4.0.8=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-webmvc-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-starter-websocket:4.0.8=conditionalTransportTestRuntimeClasspath org.springframework.boot:spring-boot-starter:3.5.16=sampleFixture -org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-web-server:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-webmvc-test:4.0.8=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.boot:spring-boot-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-starter:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test-autoconfigure:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-tomcat:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-transaction:4.0.8=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-validation:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-web-server:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-webmvc-test:4.0.8=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.boot:spring-boot-webmvc:4.0.8=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.boot:spring-boot-websocket:4.0.8=conditionalTransportTestRuntimeClasspath org.springframework.boot:spring-boot:3.5.16=sampleFixture -org.springframework.boot:spring-boot:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.cloud:spring-cloud-context:4.1.4=sampleOffTestCompileClasspath,testCompileClasspath +org.springframework.boot:spring-boot:4.0.8=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.cloud:spring-cloud-context:4.1.4=integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath org.springframework.data:spring-data-commons:3.5.13=sampleFixture -org.springframework.data:spring-data-commons:4.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.data:spring-data-commons:4.0.7=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.data:spring-data-jpa:3.5.13=sampleFixture -org.springframework.data:spring-data-jpa:4.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.data:spring-data-mongodb:5.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.graphql:spring-graphql:2.0.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.data:spring-data-jpa:4.0.7=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.data:spring-data-mongodb:5.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.graphql:spring-graphql:2.0.5=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.integration:spring-integration-core:6.5.10=sampleFixture -org.springframework.integration:spring-integration-core:7.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.integration:spring-integration-core:7.0.6=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.integration:spring-integration-jdbc:6.5.10=sampleFixture -org.springframework.integration:spring-integration-jdbc:7.0.6=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.kafka:spring-kafka:4.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.integration:spring-integration-jdbc:7.0.6=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.kafka:spring-kafka:4.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.retry:spring-retry:2.0.13=sampleFixture org.springframework.security:spring-security-config:6.5.11=sampleFixture -org.springframework.security:spring-security-config:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-config:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.security:spring-security-core:6.5.11=sampleFixture -org.springframework.security:spring-security-core:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-core:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.security:spring-security-crypto:6.5.11=sampleFixture -org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-crypto:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-oauth2-client:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.security:spring-security-oauth2-core:6.5.11=sampleFixture -org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-oauth2-core:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.security:spring-security-oauth2-jose:6.5.11=sampleFixture -org.springframework.security:spring-security-oauth2-jose:7.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.security:spring-security-oauth2-jose:7.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework.security:spring-security-oauth2-resource-server:6.5.11=sampleFixture -org.springframework.security:spring-security-oauth2-resource-server:7.0.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.springframework.security:spring-security-test:7.0.7=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.security:spring-security-oauth2-resource-server:7.0.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.security:spring-security-test:7.0.7=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework.security:spring-security-web:6.5.11=sampleFixture -org.springframework.security:spring-security-web:7.0.7=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework.session:spring-session-core:4.0.5=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework.security:spring-security-web:7.0.7=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework.session:spring-session-core:4.0.5=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework:spring-aop:6.2.19=sampleFixture -org.springframework:spring-aop:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-aop:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-aspects:6.2.19=sampleFixture -org.springframework:spring-aspects:7.0.9=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-aspects:7.0.9=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-beans:6.2.19=sampleFixture -org.springframework:spring-beans:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-context-support:7.0.9=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework:spring-beans:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context-support:7.0.9=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework:spring-context:6.2.19=sampleFixture -org.springframework:spring-context:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-context:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-core:6.2.19=sampleFixture -org.springframework:spring-core:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-core:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-expression:6.2.19=sampleFixture -org.springframework:spring-expression:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-expression:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-jcl:6.2.19=sampleFixture org.springframework:spring-jdbc:6.2.19=sampleFixture -org.springframework:spring-jdbc:7.0.9=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-jdbc:7.0.9=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-messaging:6.2.19=sampleFixture -org.springframework:spring-messaging:7.0.9=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-messaging:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework:spring-orm:6.2.19=sampleFixture -org.springframework:spring-orm:7.0.9=productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-test:7.0.9=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-orm:7.0.9=integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-test:7.0.9=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-tx:6.2.19=sampleFixture -org.springframework:spring-tx:7.0.9=compileClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-tx:7.0.9=compileClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.springframework:spring-web:6.2.19=sampleFixture -org.springframework:spring-web:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-webflux:7.0.9=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework:spring-web:7.0.9=compileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-webflux:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.springframework:spring-webmvc:6.2.19=sampleFixture -org.springframework:spring-webmvc:7.0.9=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.springframework:spring-websocket:7.0.9=conditionalTransportTestRuntimeClasspath,sampleOffTestCompileClasspath,testCompileClasspath -org.testcontainers:testcontainers-database-commons:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.testcontainers:testcontainers-jdbc:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.testcontainers:testcontainers-junit-jupiter:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.testcontainers:testcontainers-postgresql:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.testcontainers:testcontainers:2.0.5=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -org.thymeleaf:thymeleaf:3.1.5.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.unbescape:unbescape:1.1.6.RELEASE=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath -org.xerial.snappy:snappy-java:1.1.10.7=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.springframework:spring-webmvc:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.springframework:spring-websocket:7.0.9=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,sampleOffTestCompileClasspath,testCompileClasspath +org.testcontainers:testcontainers-database-commons:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:testcontainers-jdbc:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:testcontainers-junit-jupiter:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.testcontainers:testcontainers-postgresql:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.testcontainers:testcontainers-toxiproxy:2.0.5=integrationTestRuntimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.testcontainers:testcontainers:2.0.5=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.thymeleaf:thymeleaf:3.1.5.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.unbescape:unbescape:1.1.6.RELEASE=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.xerial.snappy:snappy-java:1.1.10.7=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath org.xmlresolver:xmlresolver:5.3.3=checkstyle,spotbugs -org.xmlunit:xmlunit-core:2.10.4=sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +org.xmlunit:xmlunit-core:2.10.4=integrationTestCompileClasspath,integrationTestRuntimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath org.yaml:snakeyaml:2.4=sampleFixture -org.yaml:snakeyaml:2.5=compileClasspath,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -redis.clients.authentication:redis-authx-core:0.1.1-beta2=productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath +org.yaml:snakeyaml:2.5=compileClasspath,conditionalTransportTestCompileClasspath,conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +redis.clients.authentication:redis-authx-core:0.1.1-beta2=integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestRuntimeClasspath,testRuntimeClasspath software.amazon.awssdk:annotations:2.30.0=testRuntimeClasspath software.amazon.awssdk:apache-client:2.30.0=testRuntimeClasspath software.amazon.awssdk:arns:2.30.0=testRuntimeClasspath @@ -542,7 +547,7 @@ software.amazon.awssdk:sdk-core:2.30.0=testRuntimeClasspath software.amazon.awssdk:third-party-jackson-core:2.30.0=testRuntimeClasspath software.amazon.awssdk:utils:2.30.0=testRuntimeClasspath software.amazon.eventstream:eventstream:1.0.1=testRuntimeClasspath -tools.jackson.core:jackson-core:3.1.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -tools.jackson.core:jackson-databind:3.1.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath -tools.jackson:jackson-bom:3.1.5=conditionalTransportTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +tools.jackson.core:jackson-core:3.1.5=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +tools.jackson.core:jackson-databind:3.1.5=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath +tools.jackson:jackson-bom:3.1.5=conditionalTransportTestRuntimeClasspath,integrationTestCompileClasspath,integrationTestRuntimeClasspath,productionRuntimeClasspath,runtimeClasspath,sampleOffTestCompileClasspath,sampleOffTestRuntimeClasspath,testCompileClasspath,testRuntimeClasspath empty=developmentOnly,testAndDevelopmentOnly diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java index 4a6bf359..ba72e28f 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java +++ b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/DistributedLockProviderContractTest.java @@ -12,8 +12,8 @@ import dev.caskeleton.adapter.outbound.persistence.lock.LockSettings; import dev.caskeleton.application.lock.DistributedLock; import dev.caskeleton.application.lock.DistributedLockPort; import dev.caskeleton.application.lock.LockAcquisitionTimeoutException; -import dev.caskeleton.bootstrap.lock.DistributedLockConfig; -import dev.caskeleton.bootstrap.lock.MeteredDistributedLockPort; +import dev.caskeleton.bootstrap.autoconfigure.lock.DistributedLockConfig; +import dev.caskeleton.bootstrap.autoconfigure.lock.MeteredDistributedLockPort; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import java.time.Duration; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/FlywayMigrationCompatibilityContractTest.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/IdempotencyUniqueScopeContractTest.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/PostgreSqlTestContainer.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/fileserver/FileserverRoundTripContractTest.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxAppendTransactionalContractTest.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxContainerTestSupport.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxPublisherLeaderElectionContractTest.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/OutboxRowLifecycleContractTest.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/outbox/package-info.java diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/package-info.java b/src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/package-info.java similarity index 100% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/package-info.java rename to src/app-bootstrap/src/integrationTest/java/dev/caskeleton/bootstrap/integration/package-info.java diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java index f5751345..1ef586e6 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/CaSkeletonApplication.java @@ -61,13 +61,13 @@ import org.springframework.context.annotation.FilterType; "dev.caskeleton.bootstrap.autoconfigure.fileserver", "dev.caskeleton.bootstrap.autoconfigure.httpclient", "dev.caskeleton.bootstrap.concurrency", - "dev.caskeleton.bootstrap.idempotency", - "dev.caskeleton.bootstrap.outbox", - "dev.caskeleton.bootstrap.redis", + "dev.caskeleton.bootstrap.autoconfigure.idempotency", + "dev.caskeleton.bootstrap.autoconfigure.outbox", + "dev.caskeleton.bootstrap.autoconfigure.redis", "dev.caskeleton.bootstrap.runtime", "dev.caskeleton.bootstrap.security", "dev.caskeleton.bootstrap.settings", - "dev.caskeleton.bootstrap.tracing", + "dev.caskeleton.bootstrap.autoconfigure.tracing", "dev.caskeleton.adapter.inbound.grpc", "dev.caskeleton.adapter.inbound.web", "dev.caskeleton.adapter.inbound.websocket", diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java index 9835501e..8370d3a4 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/AdapterActivationEndpoint.java @@ -1,6 +1,6 @@ package dev.caskeleton.bootstrap.activation; -import dev.caskeleton.bootstrap.autoconfigure.persistencejpa.DataSourceRequirement; +import dev.caskeleton.bootstrap.autoconfigure.jpa.DataSourceRequirement; import dev.caskeleton.shared.activation.MasterSwitch; import java.util.LinkedHashMap; import java.util.Map; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java index d42fc3c6..a606c1e8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyEnvironmentValidator.java @@ -1,5 +1,7 @@ package dev.caskeleton.bootstrap.activation; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.bootstrap.runtime.startup.StartupOrder; import java.util.List; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; @@ -44,7 +46,7 @@ public class CapabilityDependencyEnvironmentValidator implements EnvironmentPost if (violations.isEmpty()) { return; } - throw new IllegalStateException( + throw StartupFailures.requiredAdapterDisabled( "This deployment enables capabilities whose dependencies are off:" + System.lineSeparator() + String.join( @@ -53,10 +55,6 @@ public class CapabilityDependencyEnvironmentValidator implements EnvironmentPost @Override public int getOrder() { - // Alongside the master-switch and profile checks: after config data has been contributed, so - // the values read here are the ones the application will run with, and after - // MasterSwitchEnvironmentPostProcessor has rejected legacy switch spellings — a violation - // reported against a key the operator was told to stop using would send them somewhere useless. - return Ordered.LOWEST_PRECEDENCE; + return StartupOrder.CAPABILITY_DEPENDENCY; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java index e5cbeaae..6c089837 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/CapabilityDependencyStartupCheck.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.activation; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.util.List; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.env.Environment; @@ -27,7 +28,7 @@ public class CapabilityDependencyStartupCheck implements InitializingBean { if (violations.isEmpty()) { return; } - throw new IllegalStateException( + throw StartupFailures.requiredAdapterDisabled( "This deployment enables capabilities whose dependencies are off:" + System.lineSeparator() + String.join( diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java index 6dc60510..8f59918f 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/MasterSwitchEnvironmentPostProcessor.java @@ -1,5 +1,7 @@ package dev.caskeleton.bootstrap.activation; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.bootstrap.runtime.startup.StartupOrder; import dev.caskeleton.shared.activation.MasterSwitch; import dev.caskeleton.shared.activation.MasterSwitchParseResult; import dev.caskeleton.shared.activation.MasterSwitchParser; @@ -42,14 +44,14 @@ public class MasterSwitchEnvironmentPostProcessor implements EnvironmentPostProc private static void reject(MasterSwitchParseResult result) { if (result instanceof MasterSwitchParseResult.Invalid invalid) { - throw new IllegalStateException( + throw StartupFailures.envValidation( ("%s must be exactly true or false, but was \"%s\". A value this close to a boolean is a " + "deployment that believes it set the switch; it is rejected rather than read as " + "off.") .formatted(invalid.owner().property(), invalid.raw())); } if (result instanceof MasterSwitchParseResult.Ambiguous ambiguous) { - throw new IllegalStateException( + throw StartupFailures.envValidation( ("%s and %s are both set. They agree today and diverge the first time somebody edits one " + "of them; remove %s.") .formatted( @@ -58,7 +60,7 @@ public class MasterSwitchEnvironmentPostProcessor implements EnvironmentPostProc ambiguous.legacyProperty())); } if (result instanceof MasterSwitchParseResult.LegacyOnly legacy) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "%s has been replaced by %s. Set the replacement; the old key is no longer read." .formatted(legacy.legacyProperty(), legacy.replacementProperty())); } @@ -66,6 +68,6 @@ public class MasterSwitchEnvironmentPostProcessor implements EnvironmentPostProc @Override public int getOrder() { - return Ordered.LOWEST_PRECEDENCE; + return StartupOrder.MASTER_SWITCH_NORMALISATION; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java index 00206109..37f7c022 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/activation/RuntimeEnvironmentProfileValidator.java @@ -1,7 +1,9 @@ package dev.caskeleton.bootstrap.activation; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.bootstrap.runtime.startup.StartupOrder; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.util.List; -import java.util.Set; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.core.Ordered; @@ -27,11 +29,12 @@ import org.springframework.core.env.ConfigurableEnvironment; */ public class RuntimeEnvironmentProfileValidator implements EnvironmentPostProcessor, Ordered { - /** The environments a deployable artifact may declare. */ - private static final Set DEPLOYABLE_ENVIRONMENTS = Set.of("local", "dev", "prod"); - - /** Profiles that only a test-source context may activate. */ - private static final Set TEST_ONLY_ENVIRONMENTS = Set.of("test"); + // The vocabulary used to live here, as two literal Sets. It now lives in + // dev.caskeleton.shared.runtime.RuntimeEnvironment, because this validator was never its only + // reader: seven other places asked "is this production?" and answered it themselves, three of + // them with a different spelling (BOOT-002). This class still owns the *rule* — exactly one + // environment, no default, feature flags are not profiles — while the vocabulary the rule is + // written against is now a single type every leaf can share. @Override public void postProcessEnvironment( @@ -41,30 +44,30 @@ public class RuntimeEnvironmentProfileValidator implements EnvironmentPostProces } List active = List.of(environment.getActiveProfiles()); - if (active.stream().anyMatch(TEST_ONLY_ENVIRONMENTS::contains)) { + if (RuntimeEnvironment.isTestContext(active)) { return; } if (active.isEmpty()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "SPRING_PROFILES_ACTIVE is not set. A deployable runtime declares exactly one of " - + sortedDeployable() + + RuntimeEnvironment.deployableProfileNames() + "; there is no default, because a profile that is guessed is a deployment nobody " + "chose."); } - List environments = active.stream().filter(DEPLOYABLE_ENVIRONMENTS::contains).toList(); + List environments = RuntimeEnvironment.activeIn(active); if (environments.size() > 1) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "SPRING_PROFILES_ACTIVE names more than one environment " - + environments + + environments.stream().map(RuntimeEnvironment::profileName).toList() + ". Exactly one is permitted: the safety rules of two environments cannot both apply, " + "and whichever loses does so silently."); } if (environments.isEmpty()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "SPRING_PROFILES_ACTIVE=" + String.join(",", active) + " names no runtime environment. Set exactly one of " - + sortedDeployable() + + RuntimeEnvironment.deployableProfileNames() + "; a capability is selected with its master switch, never with a profile."); } } @@ -81,13 +84,8 @@ public class RuntimeEnvironmentProfileValidator implements EnvironmentPostProces && "dev.caskeleton.bootstrap.CaSkeletonApplication".equals(mainClass.getName()); } - private static List sortedDeployable() { - return DEPLOYABLE_ENVIRONMENTS.stream().sorted().toList(); - } - @Override public int getOrder() { - // Alongside the master-switch check: after config data, before anything binds a namespace. - return Ordered.LOWEST_PRECEDENCE; + return StartupOrder.RUNTIME_ENVIRONMENT_PROFILE; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java index 4fbbe27e..bd47d3d2 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/async/BackgroundJobMetrics.java @@ -1,12 +1,11 @@ package dev.caskeleton.bootstrap.async; +import dev.caskeleton.bootstrap.runtime.RuntimeWarnings; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import java.util.function.DoubleSupplier; import java.util.function.Supplier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; /** @@ -16,8 +15,6 @@ import org.springframework.beans.factory.ObjectProvider; */ public final class BackgroundJobMetrics { - private static final Logger log = LoggerFactory.getLogger(BackgroundJobMetrics.class); - static final String EXECUTOR_SATURATION = "executor.saturation"; static final String EXECUTOR_REJECTED = "executor.rejected.total"; static final String JOB_RETRY = "job.retry.total"; @@ -75,7 +72,7 @@ public final class BackgroundJobMetrics { try { builder.get().register(registry).increment(); } catch (RuntimeException ex) { - log.warn("background-job metrics: failed to record counter {}", meterName, ex); + RuntimeWarnings.meterUnavailable("background-job", meterName, ex); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java new file mode 100644 index 00000000..aa9e7be0 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/StrictSettingsBinder.java @@ -0,0 +1,83 @@ +package dev.caskeleton.bootstrap.autoconfigure; + +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import java.util.function.Predicate; +import org.springframework.boot.context.properties.bind.BindHandler; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; +import org.springframework.boot.context.properties.source.ConfigurationPropertySource; +import org.springframework.core.env.Environment; + +/** + * Binds a capability's settings strictly, and only when the capability is switched on. + * + *

Two properties matter and neither is available from a scanned {@code @ConfigurationProperties} + * class. + * + *

First, timing. A binder runs inside its master-gated auto-configuration, so a deployment that + * never enables a capability never binds one of its settings — a malformed {@code Duration} in a + * block nobody switched on cannot fail that deployment's startup. + * + *

Second, strictness. Unknown keys under the prefix are refused rather than ignored. Silently + * dropping {@code app.fileserver-platform.uplaod.max-file-size} is how a deployment ends up running + * the default limit while its configuration file says otherwise, and discovering that from a + * production incident is the whole failure mode this prevents. + * + *

Why this is shared

+ * + *

The Fileserver and HTTP client platforms each had their own copy of this — same handler, same + * {@code Binder.get(...).bind(PREFIX, Bindable.of(T), strict)}, same {@code orElseThrow}, differing + * only in which prefix they named and in one word of the failure message (BOOT-011). The one real + * difference between them is which property sources the unbound-element check applies to, so that + * is the parameter, and everything else is here once. + */ +public final class StrictSettingsBinder { + + private StrictSettingsBinder() {} + + /** + * Binds the prefix strictly against every property source. + * + * @param environment the environment to bind from + * @param prefix the settings prefix + * @param type the settings type + * @param the settings type + * @return the bound settings + */ + public static T bind(Environment environment, String prefix, Class type) { + return bind(environment, prefix, type, source -> true); + } + + /** + * Binds the prefix strictly, checking unbound elements only in the sources the filter accepts. + * + *

The filter exists for one real case rather than as generality for its own sake. Spring + * enumerates {@code APP_HTTPCLIENT_CLIENTS_0_BASE_URL} from the system environment as {@code + * ...clients[0].base.url} while binding it as {@code ...clients[0].base-url}, so leaving the + * system environment to {@link NoUnboundElementsBindHandler} would reject every correctly spelled + * variable and still miss the misspelled ones. A caller that excludes it here is expected to + * check that source by another route. + * + * @param environment the environment to bind from + * @param prefix the settings prefix + * @param type the settings type + * @param unboundCheckScope which property sources the unbound-element check applies to + * @param the settings type + * @return the bound settings + */ + public static T bind( + Environment environment, + String prefix, + Class type, + Predicate unboundCheckScope) { + BindHandler strict = + new NoUnboundElementsBindHandler(BindHandler.DEFAULT, unboundCheckScope::test); + return Binder.get(environment) + .bind(prefix, Bindable.of(type), strict) + .orElseThrow( + () -> + StartupFailures.envValidation( + prefix + " could not be bound although the capability is enabled")); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java index 65e9140d..c0ad290b 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformAutoConfiguration.java @@ -17,6 +17,7 @@ import dev.caskeleton.adapter.inbound.web.fileserver.security.FileserverRequestC import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusChecksumVerifier; import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusProperties; import dev.caskeleton.adapter.inbound.web.fileserver.tus.TusRequestParser; +import dev.caskeleton.adapter.outbound.fileserver.platform.observability.MicrometerFileserverMetrics; import dev.caskeleton.application.fileserver.api.StorageNamespace; import dev.caskeleton.application.fileserver.api.transfer.ConditionalRequestEvaluator; import dev.caskeleton.application.fileserver.api.transfer.ContentDispositionFactory; @@ -28,6 +29,7 @@ import dev.caskeleton.application.fileserver.download.DownloadPolicy; import dev.caskeleton.application.fileserver.observability.FileserverMetricsPort; import dev.caskeleton.application.fileserver.observability.SafeFileFingerprint; import dev.caskeleton.application.fileserver.upload.UploadPolicy; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import io.micrometer.core.instrument.MeterRegistry; import java.nio.charset.StandardCharsets; import java.util.List; @@ -297,7 +299,7 @@ public class FileserverPlatformAutoConfiguration { SafeFileFingerprint fileserverFingerprint(FileserverPlatformSettings properties) { String key = properties.observability().fingerprintKey(); if (key == null || key.isBlank()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( FileserverPlatformSettings.PREFIX + ".observability.fingerprint-key must be configured while metrics are enabled: an " + "unkeyed digest of an enumerable identifier is reversible"); diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java index 2d495cdb..c53c9715 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettings.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.autoconfigure.fileserver; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.nio.file.Path; import java.time.Duration; import java.util.List; @@ -77,7 +78,7 @@ public record FileserverPlatformSettings( requireText(publishMode, "storage.publish-mode"); long bytes = requireSize(bufferSize, "storage.buffer-size"); if (bytes < MINIMUM_BUFFER_BYTES || bytes > MAXIMUM_BUFFER_BYTES) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".storage.buffer-size must be between 4KB and 8MB"); } } @@ -99,13 +100,13 @@ public record FileserverPlatformSettings( long maxRequest = requireSize(maxRequestSize, "upload.max-request-size"); long reservation = requireSize(initialReservation, "upload.initial-reservation"); if (maxRequest < maxFile) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".upload.max-request-size must be at least upload.max-file-size: a request " + "envelope smaller than the file it carries rejects every maximum-sized upload"); } if (reservation > maxFile) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".upload.initial-reservation must not exceed upload.max-file-size"); } requirePositive(maxParts, "upload.max-parts"); @@ -152,7 +153,7 @@ public record FileserverPlatformSettings( requirePositive(queueCapacity, "transfer.queue-capacity"); requirePositive(awaitSeconds, "transfer.await-seconds"); if (coreSize > maxSize) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".transfer.core-size must not exceed transfer.max-size"); } } @@ -235,11 +236,11 @@ public record FileserverPlatformSettings( requireFraction(softHighWater, "quota.soft-high-water"); requireFraction(hardHighWater, "quota.hard-high-water"); if (softHighWater >= hardHighWater) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".quota.soft-high-water must be below quota.hard-high-water"); } if (scopeUploadPermits > instanceUploadPermits) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".quota.scope-upload-permits must not exceed quota.instance-upload-permits"); } } @@ -298,13 +299,13 @@ public record FileserverPlatformSettings( private static void requireText(String value, String name) { if (value == null || value.isBlank()) { - throw new IllegalStateException(PREFIX + "." + name + " must be non-blank"); + throw StartupFailures.envValidation(PREFIX + "." + name + " must be non-blank"); } } private static void requireRoles(List roles, String name) { if (roles == null || roles.isEmpty()) { - throw new IllegalStateException(PREFIX + "." + name + " must list at least one role"); + throw StartupFailures.envValidation(PREFIX + "." + name + " must list at least one role"); } for (String role : roles) { requireText(role, name); @@ -313,19 +314,19 @@ public record FileserverPlatformSettings( private static void requirePositive(int value, String name) { if (value < 1) { - throw new IllegalStateException(PREFIX + "." + name + " must be positive"); + throw StartupFailures.envValidation(PREFIX + "." + name + " must be positive"); } } private static void requirePositive(Duration value, String name) { if (value == null || value.isNegative() || value.isZero()) { - throw new IllegalStateException(PREFIX + "." + name + " must be a positive duration"); + throw StartupFailures.envValidation(PREFIX + "." + name + " must be a positive duration"); } } private static void requireFraction(double value, String name) { if (!(value > 0) || value > 1) { - throw new IllegalStateException(PREFIX + "." + name + " must be within (0, 1]"); + throw StartupFailures.envValidation(PREFIX + "." + name + " must be within (0, 1]"); } } @@ -337,14 +338,14 @@ public record FileserverPlatformSettings( */ private static long requireSize(DataSize value, String name) { if (value == null || value.toBytes() < 1) { - throw new IllegalStateException(PREFIX + "." + name + " must be a positive size"); + throw StartupFailures.envValidation(PREFIX + "." + name + " must be a positive size"); } return value.toBytes(); } private static void requireAbsolute(Path root) { if (root == null || !root.isAbsolute()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( PREFIX + ".storage.root must be an absolute path: a relative root resolves against the " + "process working directory, which differs between a container and a test"); diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java index 0b1df654..599e24fb 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverPlatformSettingsBinder.java @@ -1,41 +1,24 @@ package dev.caskeleton.bootstrap.autoconfigure.fileserver; -import org.springframework.boot.context.properties.bind.BindHandler; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; +import dev.caskeleton.bootstrap.autoconfigure.StrictSettingsBinder; import org.springframework.core.env.Environment; /** * Binds {@link FileserverPlatformSettings} strictly, and only when asked. * - *

Two properties matter here and neither is available from a scanned - * {@code @ConfigurationProperties} class. - * - *

First, timing. This runs inside the master-gated auto-configuration, so a deployment that - * never enables the Fileserver never binds a Fileserver setting — a malformed {@code Duration} in a - * block nobody switched on cannot fail its startup. - * - *

Second, strictness. Unknown keys under the prefix are refused rather than ignored. Silently - * dropping {@code app.fileserver-platform.uplaod.max-file-size} is how a deployment ends up running - * the default limit while its configuration file says otherwise, and a file capability is exactly - * the wrong place to discover that from a production incident. + *

The mechanics — master-gated timing, refusing unknown keys under the prefix — live in {@link + * StrictSettingsBinder}, which this and the HTTP client platform's binder used to hold a copy of + * each (BOOT-011). What is left here is the one thing that is actually specific: which settings + * type is bound. Every property source is in scope for the unbound-element check, because this + * prefix is populated from YAML placeholders rather than bound directly from the system + * environment. */ final class FileserverPlatformSettingsBinder { private FileserverPlatformSettingsBinder() {} static FileserverPlatformSettings bind(Environment environment) { - BindHandler strict = new NoUnboundElementsBindHandler(BindHandler.DEFAULT); - return Binder.get(environment) - .bind( - FileserverPlatformSettings.PREFIX, - Bindable.of(FileserverPlatformSettings.class), - strict) - .orElseThrow( - () -> - new IllegalStateException( - FileserverPlatformSettings.PREFIX - + " could not be bound although the capability is enabled")); + return StrictSettingsBinder.bind( + environment, FileserverPlatformSettings.PREFIX, FileserverPlatformSettings.class); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java index 9bbb9cb0..bcc76234 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverSecurityConfiguration.java @@ -7,6 +7,8 @@ import dev.caskeleton.adapter.outbound.fileserver.platform.security.UnenforcedFi import dev.caskeleton.application.fileserver.admin.AdminAuditPort; import dev.caskeleton.application.fileserver.api.security.FileAccessPolicy; import dev.caskeleton.application.fileserver.observability.FileserverAuditPort; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; @@ -34,8 +36,6 @@ public class FileserverSecurityConfiguration { private static final String MODE_ROLE_BASED = "role-based"; private static final String MODE_UNENFORCED = "unenforced"; - private static final Set PRODUCTION_PROFILES = Set.of("prod", "production"); - @Bean @ConditionalOnMissingBean FileAccessPolicy fileserverAccessPolicy( @@ -55,11 +55,11 @@ public class FileserverSecurityConfiguration { false); case MODE_UNENFORCED -> unenforcedOrFail(environment); case MODE_REQUIRED -> - throw new IllegalStateException( + throw StartupFailures.envValidation( "fileserver is enabled without an authorization policy: supply a FileAccessPolicy " + "bean, or set app.fileserver-platform.security.access-policy to role-based"); default -> - throw new IllegalStateException( + throw StartupFailures.envValidation( "app.fileserver-platform.security.access-policy must be one of required, role-based, " + "unenforced"); }; @@ -78,14 +78,12 @@ public class FileserverSecurityConfiguration { } private static FileAccessPolicy unenforcedOrFail(Environment environment) { - for (String profile : environment.getActiveProfiles()) { - if (PRODUCTION_PROFILES.contains(profile.trim().toLowerCase(Locale.ROOT))) { - throw new IllegalStateException( - "app.fileserver-platform.security.access-policy=unenforced authorizes every file " - + "operation and must not be active under the '" - + profile - + "' profile"); - } + if (RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles()))) { + throw StartupFailures.profileMismatch( + "app.fileserver-platform.security.access-policy=unenforced authorizes every file " + + "operation and must not be active under the '" + + RuntimeEnvironment.PROD.profileName() + + "' profile"); } return new UnenforcedFileAccessPolicy(); } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java index b974e8e5..d93e62e8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupConfiguration.java @@ -7,8 +7,8 @@ import dev.caskeleton.adapter.outbound.fileserver.platform.security.UnenforcedFi import dev.caskeleton.application.fileserver.api.metadata.FileMetadataStore; import dev.caskeleton.application.fileserver.api.security.FileAccessPolicy; import dev.caskeleton.application.fileserver.upload.FileVerificationService; -import java.util.Locale; -import java.util.Set; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; +import java.util.List; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -30,8 +30,6 @@ import org.springframework.core.env.Environment; @Configuration(proxyBeanMethods = false) public class FileserverStartupConfiguration { - private static final Set PRODUCTION_PROFILES = Set.of("prod", "production"); - @Bean @ConditionalOnMissingBean FileserverStartupValidator fileserverStartupValidator() { @@ -88,11 +86,6 @@ public class FileserverStartupConfiguration { } private static boolean isProductionProfile(Environment environment) { - for (String profile : environment.getActiveProfiles()) { - if (PRODUCTION_PROFILES.contains(profile.trim().toLowerCase(Locale.ROOT))) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java index 9e58e350..3ce92afb 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStartupValidator.java @@ -2,9 +2,9 @@ package dev.caskeleton.bootstrap.autoconfigure.fileserver; import dev.caskeleton.adapter.outbound.fileserver.platform.local.LocalStorageProbeResult; import dev.caskeleton.application.fileserver.api.content.PublishMode; +import dev.caskeleton.bootstrap.runtime.startup.StartupPhase; +import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; /** * Fail-closed startup gate for the Fileserver platform. @@ -16,6 +16,9 @@ import java.util.List; */ public final class FileserverStartupValidator { + /** Shared lead-in for both entry points; the collected violations follow it. */ + private static final String SUMMARY = "fileserver startup validation failed"; + /** * Validates the publish mode against what the storage probe actually proved. * @@ -23,9 +26,10 @@ public final class FileserverStartupValidator { * allowed to fall back to a metadata pointer publish, so it only requires the mandatory checks. */ public void validate(PublishMode publishMode, LocalStorageProbeResult probeResult) { - List violations = new ArrayList<>(); + StartupValidationContext violations = + StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION); collectStorageViolations(publishMode, probeResult, violations); - failOn(violations); + violations.failIfViolated(SUMMARY); } /** @@ -35,47 +39,51 @@ public final class FileserverStartupValidator { * rather than discovering them one restart at a time. */ public void validate(FileserverStartupCheck check) { - List violations = new ArrayList<>(); + StartupValidationContext violations = + StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION); collectStorageViolations(check.publishMode(), check.probeResult(), violations); collectRootViolations(check, violations); if (check.multiInstance() && !check.metadataStorePresent()) { - violations.add("multi-instance mode requires a shared metadata store"); + violations.reject("multi-instance mode requires a shared metadata store"); } if (check.productionProfile() && check.allowAllAccessPolicy()) { - violations.add( + violations.reject( "a no-op allow-all FileAccessPolicy must not be active in a production profile"); } if (check.scannerRequired() && !check.scannerPresent()) { - violations.add("scanner-required is set but no file verifier bean is registered"); + violations.reject("scanner-required is set but no file verifier bean is registered"); } if (check.nginxDelegationEnabled() && !check.nginxInternalMappingValidated()) { - violations.add("nginx delegation is enabled without a validated internal URI mapping"); + violations.reject("nginx delegation is enabled without a validated internal URI mapping"); } - failOn(violations); + violations.failIfViolated(SUMMARY); } private void collectStorageViolations( - PublishMode publishMode, LocalStorageProbeResult probeResult, List violations) { + PublishMode publishMode, + LocalStorageProbeResult probeResult, + StartupValidationContext violations) { if (!probeResult.writableRoot()) { - violations.add("storage root is not writable"); + violations.reject("storage root is not writable"); } if (!probeResult.atomicCreate()) { - violations.add("storage root does not provide atomic create"); + violations.reject("storage root does not provide atomic create"); } if (!probeResult.sameFileStore()) { - violations.add("staging, content, and quarantine are not on the same file store"); + violations.reject("staging, content, and quarantine are not on the same file store"); } if (!probeResult.symlinkNoFollow()) { - violations.add("storage root does not refuse to follow symbolic links"); + violations.reject("storage root does not refuse to follow symbolic links"); } if (!probeResult.descriptorRelativeAccess()) { - violations.add( + violations.reject( "storage root does not support descriptor-relative access, so every open would " + "re-resolve a pathname and leave the parent-replacement race open"); } if (publishMode == PublishMode.ATOMIC_MOVE_REQUIRED && !probeResult.atomicMove()) { - violations.add("publish mode requires an atomic move that the storage probe could not prove"); + violations.reject( + "publish mode requires an atomic move that the storage probe could not prove"); } } @@ -85,21 +93,15 @@ public final class FileserverStartupValidator { *

Storing uploads under a web root or a config root is what turns an upload into remote code * execution, so this is checked before anything is written. */ - private void collectRootViolations(FileserverStartupCheck check, List violations) { + private void collectRootViolations( + FileserverStartupCheck check, StartupValidationContext violations) { Path root = check.storageRoot().toAbsolutePath().normalize(); for (Path forbidden : check.forbiddenRootAncestors()) { Path normalized = forbidden.toAbsolutePath().normalize(); if (root.startsWith(normalized) || normalized.startsWith(root)) { - violations.add("storage root overlaps a web root or configuration root"); + violations.reject("storage root overlaps a web root or configuration root"); return; } } } - - private static void failOn(List violations) { - if (!violations.isEmpty()) { - throw new IllegalStateException( - "fileserver startup validation failed: " + String.join("; ", violations)); - } - } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java index a40d1212..fd947c93 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/fileserver/FileserverStorageConfiguration.java @@ -43,6 +43,7 @@ import dev.caskeleton.application.fileserver.recovery.StagingUploadLocator; import dev.caskeleton.application.fileserver.upload.FileVerificationService; import dev.caskeleton.application.fileserver.upload.UploadContentGateway; import dev.caskeleton.application.fileserver.upload.UploadStorageGateway; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.nio.file.Path; import java.time.Clock; import java.util.List; @@ -112,7 +113,7 @@ public class FileserverStorageConfiguration { LocalStorageProbeResult fileserverStorageProbeResult(LocalStorageProperties properties) { LocalStorageProbeResult result = new LocalStorageCapabilityProbe(properties).run(); if (!result.mandatoryChecksPassed()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "fileserver storage root failed a mandatory capability probe: " + result.failures() + "; the capability refuses to start rather than publish files it cannot place " @@ -264,7 +265,7 @@ public class FileserverStorageConfiguration { for (Path forbidden : storage.forbiddenRootAncestors()) { Path ancestor = forbidden.toAbsolutePath().normalize(); if (root.startsWith(ancestor)) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "fileserver storage root must not live under a configured forbidden ancestor"); } } @@ -275,7 +276,7 @@ public class FileserverStorageConfiguration { try { return PublishMode.valueOf(canonical); } catch (IllegalArgumentException unknown) { - throw new IllegalStateException( + throw StartupFailures.envValidation( FileserverPlatformSettings.PREFIX + ".storage.publish-mode must be one of atomic-move-required, " + "atomic-move-preferred, metadata-pointer", diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java index 066d68ac..3ab25afd 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientAuthenticationAutoConfiguration.java @@ -8,6 +8,7 @@ import dev.caskeleton.adapter.outbound.httpclient.auth.OAuth2CredentialProvider; import dev.caskeleton.adapter.outbound.httpclient.auth.ReactiveCredentialProviderRegistry; import dev.caskeleton.adapter.outbound.httpclient.auth.ReactiveRequestCredentialProvider; import dev.caskeleton.adapter.outbound.httpclient.auth.StaticBearerCredentialProvider; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.time.Clock; import java.util.Set; import java.util.function.Function; @@ -36,7 +37,7 @@ public class HttpClientAuthenticationAutoConfiguration { @ConditionalOnMissingBean(name = "httpClientSecretResolver") Function httpClientSecretResolver() { return reference -> { - throw new IllegalStateException( + throw StartupFailures.envValidation( "no http client secret resolver is configured for reference '" + reference + "'"); }; } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java index 328e9ef8..c554e4c3 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientEnvironmentKeys.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.autoconfigure.httpclient; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.lang.reflect.ParameterizedType; import java.lang.reflect.RecordComponent; import java.util.LinkedHashMap; @@ -84,7 +85,7 @@ final class HttpClientEnvironmentKeys { } } if (!unrecognised.isEmpty()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "unrecognised HTTP client environment variables " + unrecognised + "; see docs/httpclient/env-fields.yaml for the accepted names"); diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java index 46e4298d..79cff694 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettings.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.autoconfigure.httpclient; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.time.Duration; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -51,7 +52,7 @@ public record HttpClientPlatformSettings( // An active platform with nothing to call still holds transport providers, a resilience // registry and five caller-facing gateways that no caller can reach. That is not a working // deployment with an empty configuration; it is a configuration mistake with a running cost. - throw new IllegalStateException( + throw StartupFailures.envValidation( "HTTPCLIENT_ACTIVE_WITHOUT_CLIENTS: " + PREFIX + ".enabled is true but no client is declared under " @@ -76,16 +77,16 @@ public record HttpClientPlatformSettings( Map byNormalisedForm = new LinkedHashMap<>(); for (String name : names) { if (name == null || name.isBlank()) { - throw new IllegalStateException(where + "[*].name must be non-blank"); + throw StartupFailures.envValidation(where + "[*].name must be non-blank"); } if (seen.contains(name)) { - throw new IllegalStateException(where + " declares '" + name + "' more than once"); + throw StartupFailures.envValidation(where + " declares '" + name + "' more than once"); } seen.add(name); String normalised = name.toUpperCase(Locale.ROOT).replaceAll("[^A-Z0-9]", ""); String previous = byNormalisedForm.putIfAbsent(normalised, name); if (previous != null) { - throw new IllegalStateException( + throw StartupFailures.envValidation( where + " declares '" + previous diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java index efbf5c61..1ea95d3c 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientPlatformSettingsBinder.java @@ -1,34 +1,22 @@ package dev.caskeleton.bootstrap.autoconfigure.httpclient; -import org.springframework.boot.context.properties.bind.BindHandler; -import org.springframework.boot.context.properties.bind.Bindable; -import org.springframework.boot.context.properties.bind.Binder; -import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler; +import dev.caskeleton.bootstrap.autoconfigure.StrictSettingsBinder; import org.springframework.core.env.Environment; import org.springframework.core.env.SystemEnvironmentPropertySource; /** * Binds {@link HttpClientPlatformSettings} strictly, and only when asked. * - *

Two properties matter here and neither is available from a scanned - * {@code @ConfigurationProperties} class. + *

The mechanics live in {@link StrictSettingsBinder} (BOOT-011). What stays here is this + * platform's one genuine difference: strictness needs two mechanisms because one does not cover + * both surfaces. * - *

First, timing. This runs inside the master-gated auto-configuration, so a deployment that - * never enables the platform never binds one of its settings — a malformed {@code Duration} in a - * block nobody switched on cannot fail its startup. - * - *

Second, strictness. Unknown keys under the prefix are refused rather than ignored. Silently - * dropping {@code app.httpclient.clients[0].timeuot.total-call} leaves the client running the - * default four-second budget while the configuration file says otherwise, which is exactly the kind - * of divergence an outbound call platform should never make an operator discover from an incident. - * - *

Strictness is delivered by two mechanisms because one does not cover both surfaces. Property - * sources that enumerate their keys in property form — YAML, properties files, test overrides — are - * checked by {@link NoUnboundElementsBindHandler}. The system environment is excluded from that - * handler and checked by {@link HttpClientEnvironmentKeys#rejectUnrecognised} instead: Spring - * enumerates {@code APP_HTTPCLIENT_CLIENTS_0_BASE_URL} as {@code ...clients[0].base.url} while - * binding it as {@code ...clients[0].base-url}, so leaving it to the handler would reject every - * correctly spelled variable and still miss the misspelled ones. + *

Property sources that enumerate their keys in property form — YAML, properties files, test + * overrides — are checked by the shared binder's unbound-element handler. The system environment is + * excluded from it and checked by {@link HttpClientEnvironmentKeys#rejectUnrecognised} instead: + * Spring enumerates {@code APP_HTTPCLIENT_CLIENTS_0_BASE_URL} as {@code ...clients[0].base.url} + * while binding it as {@code ...clients[0].base-url}, so leaving it to the handler would reject + * every correctly spelled variable and still miss the misspelled ones. */ final class HttpClientPlatformSettingsBinder { @@ -36,19 +24,10 @@ final class HttpClientPlatformSettingsBinder { static HttpClientPlatformSettings bind(Environment environment) { HttpClientEnvironmentKeys.rejectUnrecognised(environment); - BindHandler strict = - new NoUnboundElementsBindHandler( - BindHandler.DEFAULT, - source -> !(source.getUnderlyingSource() instanceof SystemEnvironmentPropertySource)); - return Binder.get(environment) - .bind( - HttpClientPlatformSettings.PREFIX, - Bindable.of(HttpClientPlatformSettings.class), - strict) - .orElseThrow( - () -> - new IllegalStateException( - HttpClientPlatformSettings.PREFIX - + " could not be bound although the platform is enabled")); + return StrictSettingsBinder.bind( + environment, + HttpClientPlatformSettings.PREFIX, + HttpClientPlatformSettings.class, + source -> !(source.getUnderlyingSource() instanceof SystemEnvironmentPropertySource)); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java index b85dff42..3b9856d8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientProfileAutoConfiguration.java @@ -19,6 +19,7 @@ import dev.caskeleton.adapter.outbound.httpclient.transport.ReactiveTransportPro import dev.caskeleton.adapter.outbound.httpclient.transport.TransportLifecycleListener; import dev.caskeleton.adapter.outbound.httpclient.webclient.WebClientRuntimeFactory; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.random.RandomGenerator; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -96,12 +97,21 @@ public class HttpClientProfileAutoConfiguration { return new ClientRuntimeRegistry(runtimes); } + /** + * Maps the active profiles onto the HTTP client platform's own production/non-production split. + * + *

Two types named {@code RuntimeEnvironment} meet here and they are different concepts, so the + * shared one is spelled out in full rather than imported. {@code + * dev.caskeleton.shared.runtime.RuntimeEnvironment} is the repository-wide profile vocabulary — + * the single answer to "which environment is this?" (BOOT-002). The imported {@code + * ...httpclient.profile.RuntimeEnvironment} is this adapter's binary hardening switch. Deriving + * the second from the first is the point: the adapter keeps its own vocabulary while the question + * "is this production?" is answered in exactly one place. + */ private RuntimeEnvironment runtimeEnvironment(Environment environment) { - for (String profile : environment.getActiveProfiles()) { - if ("prod".equalsIgnoreCase(profile) || "production".equalsIgnoreCase(profile)) { - return RuntimeEnvironment.PRODUCTION; - } - } - return RuntimeEnvironment.NON_PRODUCTION; + return dev.caskeleton.shared.runtime.RuntimeEnvironment.isProductionActive( + List.of(environment.getActiveProfiles())) + ? RuntimeEnvironment.PRODUCTION + : RuntimeEnvironment.NON_PRODUCTION; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java new file mode 100644 index 00000000..3e94322b --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyComposition.java @@ -0,0 +1,39 @@ +package dev.caskeleton.bootstrap.autoconfigure.idempotency; + +/** + * The bean shape a selected idempotency provider requires the composition root to hold. + * + *

This used to be a {@code switch} in {@code IdempotencyProviderSelectionConfig} — so adding a + * provider meant editing two places, the enum and the switch, and forgetting the second still + * compiled (BOOT-010). Now the requirement is a constructor argument of the enum constant, which + * means it cannot be added without being stated. + * + *

It stays here rather than being contributed by the provider modules. A count of beans in the + * composition root is assembly knowledge: the Redis adapter has no view of how many stores this + * deployment ended up holding, and asking it to declare one would give an adapter an opinion about + * a graph it is only a part of. + * + * @param jdbcStores V1 {@code IdempotencyStorePort} beans expected + * @param jdbcExecutors V1 {@code IdempotencyExecutor} beans expected + * @param ownerSafeStores V2 {@code IdempotencyStorePortV2} beans expected + * @param ownerSafeExecutors V2 {@code IdempotencyExecutorV2} beans expected + */ +record IdempotencyComposition( + int jdbcStores, int jdbcExecutors, int ownerSafeStores, int ownerSafeExecutors) { + + /** + * Redis and PostgreSQL declare the same shape because they satisfy one contract: the count is by + * type, so the guard cannot tell them apart and does not need to. What it refuses is a deployment + * holding two stores, or a store with nothing to drive it, whichever provider produced them. + */ + String describe() { + return jdbcStores + + " JDBC V1 store(s), " + + jdbcExecutors + + " JDBC V1 executor(s), " + + ownerSafeStores + + " owner-safe V2 store(s) and " + + ownerSafeExecutors + + " owner-safe V2 executor(s)"; + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyConfig.java similarity index 94% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyConfig.java index c3bdc4dc..018dcb92 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import dev.caskeleton.application.idempotency.IdempotencyExecutor; import dev.caskeleton.application.idempotency.IdempotencyStorePort; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSelectionConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSelectionConfig.java similarity index 74% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSelectionConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSelectionConfig.java index c6209926..58c8ca94 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSelectionConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSelectionConfig.java @@ -1,9 +1,10 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import dev.caskeleton.application.idempotency.IdempotencyExecutor; import dev.caskeleton.application.idempotency.IdempotencyStorePort; import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2; import dev.caskeleton.application.idempotency.v2.IdempotencyStorePortV2; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -44,20 +45,13 @@ public class IdempotencyProviderSelectionConfig { ObjectProvider ownerSafeStores, ObjectProvider ownerSafeExecutors) { return () -> { - Counts found = - new Counts( + IdempotencyComposition found = + new IdempotencyComposition( count(jdbcStores), count(jdbcExecutors), count(ownerSafeStores), count(ownerSafeExecutors)); - switch (settings.provider()) { - case DISABLED -> require(found, new Counts(0, 0, 0, 0)); - case JDBC -> require(found, new Counts(1, 1, 0, 0)); - case REDIS, POSTGRESQL -> require(found, new Counts(0, 0, 1, 1)); - default -> - throw new IllegalStateException( - "Unsupported idempotency provider: " + settings.provider()); - } + require(found, settings.provider().composition()); }; } @@ -65,29 +59,13 @@ public class IdempotencyProviderSelectionConfig { return Math.toIntExact(beans.stream().count()); } - private static void require(Counts found, Counts expected) { + private static void require(IdempotencyComposition found, IdempotencyComposition expected) { if (!found.equals(expected)) { - throw new IllegalStateException( + throw StartupFailures.requiredAdapterDisabled( "Idempotency provider selection is ambiguous or incomplete: expected " + expected.describe() + ", but found " + found.describe()); } } - - /** The four bean counts the selection is exact about, in one comparable value. */ - private record Counts( - int jdbcStores, int jdbcExecutors, int ownerSafeStores, int ownerSafeExecutors) { - - String describe() { - return jdbcStores - + " JDBC V1 store(s), " - + jdbcExecutors - + " JDBC V1 executor(s), " - + ownerSafeStores - + " owner-safe V2 store(s) and " - + ownerSafeExecutors - + " owner-safe V2 executor(s)"; - } - } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java new file mode 100644 index 00000000..c77da6f5 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderSettings.java @@ -0,0 +1,67 @@ +package dev.caskeleton.bootstrap.autoconfigure.idempotency; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.bind.ConstructorBinding; + +/** Exact provider selector; provider precedence is never inferred from the classpath. */ +@ConfigurationProperties(prefix = "ca-skeleton.capabilities.idempotency") +public record IdempotencyProviderSettings(Provider provider) { + + @ConstructorBinding + public IdempotencyProviderSettings { + provider = provider == null ? Provider.JDBC : provider; + } + + /** + * The stores a deployment may select. + * + *

{@code POSTGRESQL} selects the owner-safe V2 store in the JPA leaf. It had no value here for + * as long as it existed, so the only way to reach it was to construct it in a test: a capability + * with an implementation, a schema stream and an integration suite that no deployment could turn + * on. A store nobody can select is not a feature, and leaving the selector out did not make the + * code any less present in the artifact. + */ + public enum Provider { + + /** Nothing composed — the capability is off. */ + DISABLED(0, 0, 0, 0), + + /** The JDBC V1 store and its executor. */ + JDBC(1, 1, 0, 0), + + /** The owner-safe V2 store and its executor. */ + REDIS(0, 0, 1, 1), + + /** The owner-safe V2 store and its executor — the same contract Redis satisfies. */ + POSTGRESQL(0, 0, 1, 1); + + private final int jdbcStores; + private final int jdbcExecutors; + private final int ownerSafeStores; + private final int ownerSafeExecutors; + + Provider(int jdbcStores, int jdbcExecutors, int ownerSafeStores, int ownerSafeExecutors) { + this.jdbcStores = jdbcStores; + this.jdbcExecutors = jdbcExecutors; + this.ownerSafeStores = ownerSafeStores; + this.ownerSafeExecutors = ownerSafeExecutors; + } + + /** + * The bean shape this provider requires the composition root to hold. + * + *

Carried by the constant rather than decided by a {@code switch} elsewhere, so a fifth + * provider cannot be added without saying what it needs — the compiler asks for the four + * arguments (BOOT-010). + * + *

Held as four ints rather than as a shared {@link IdempotencyComposition} constant because + * Error Prone's {@code ImmutableEnumChecker} refuses a reference field in an enum unless its + * type carries {@code @Immutable}, and that annotation is not a dependency of this build. + * Adding one to satisfy a checker would be a worse trade than four primitives. + */ + IdempotencyComposition composition() { + return new IdempotencyComposition( + jdbcStores, jdbcExecutors, ownerSafeStores, ownerSafeExecutors); + } + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettings.java similarity index 95% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettings.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettings.java index 31cdd071..fcc771d7 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettings.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.time.Duration; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/PostgreSqlIdempotencyProviderConfig.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/PostgreSqlIdempotencyProviderConfig.java index e9f35eb4..1ba12fdc 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/PostgreSqlIdempotencyProviderConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/PostgreSqlIdempotencyProviderConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import dev.caskeleton.adapter.outbound.persistence.postgresql.idempotency.PostgreSqlOwnerSafeIdempotencyStore; import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DataSourceRequirement.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DataSourceRequirement.java index 71537d32..bf7d81af 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/DataSourceRequirement.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DataSourceRequirement.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.autoconfigure.persistencejpa; +package dev.caskeleton.bootstrap.autoconfigure.jpa; import dev.caskeleton.shared.activation.MasterSwitch; import java.util.ArrayList; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/DatabaseReadinessGroupPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DatabaseReadinessGroupPostProcessor.java similarity index 61% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/DatabaseReadinessGroupPostProcessor.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DatabaseReadinessGroupPostProcessor.java index ee0ddc48..977e972b 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/DatabaseReadinessGroupPostProcessor.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/DatabaseReadinessGroupPostProcessor.java @@ -1,16 +1,11 @@ -package dev.caskeleton.bootstrap.runtime; +package dev.caskeleton.bootstrap.autoconfigure.jpa; -import dev.caskeleton.bootstrap.autoconfigure.persistencejpa.DataSourceRequirement; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; +import dev.caskeleton.bootstrap.runtime.ReadinessGroup; +import dev.caskeleton.bootstrap.runtime.startup.StartupOrder; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; /** * Adds the database readiness contributor to the readiness group, and only when it will exist. @@ -40,32 +35,13 @@ public class DatabaseReadinessGroupPostProcessor implements EnvironmentPostProce if (!DataSourceRequirement.isRequired(environment)) { return; } - String configured = - environment.getProperty(RedisReadinessGroupPostProcessor.READINESS_INCLUDE, ""); - Set members = new LinkedHashSet<>(); - Arrays.stream(configured.split(",")) - .map(String::trim) - .filter(member -> !member.isEmpty()) - .forEach(members::add); - if (!members.add(DB_CONTRIBUTOR)) { - // Already named explicitly. Repeating it would be harmless and would also hide that the - // operator asked for it themselves. - return; - } - environment - .getPropertySources() - .addFirst( - new MapPropertySource( - PROPERTY_SOURCE, - Map.of( - RedisReadinessGroupPostProcessor.READINESS_INCLUDE, - String.join(",", members)))); + ReadinessGroup.include(environment, PROPERTY_SOURCE, DB_CONTRIBUTOR); } @Override public int getOrder() { // After config data, for the same reason the Redis one does; and after the Redis processor, so // the two appends chain deterministically instead of racing for the same property. - return ConfigDataEnvironmentPostProcessor.ORDER + 2; + return StartupOrder.READINESS_DATABASE; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java index d3b18bea..6d60e038 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDangerousConfigurationGuard.java @@ -1,6 +1,7 @@ package dev.caskeleton.bootstrap.autoconfigure.jpa; import dev.caskeleton.adapter.outbound.persistence.migration.FlywaySchemaPolicy; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.util.Arrays; import java.util.Objects; import org.springframework.core.env.Environment; @@ -48,7 +49,7 @@ public final class JpaDangerousConfigurationGuard { if (Boolean.TRUE.equals(environment.getProperty(OPEN_IN_VIEW_KEY, Boolean.class)) && !localConvenience) { - throw new IllegalStateException( + throw StartupFailures.profileMismatch( OPEN_IN_VIEW_KEY + " must be false outside a local convenience profile: it holds the persistence" + " context open through view rendering, so a lazy association touched by the" diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java index 16b80ecd..10ecbfe8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaDataSourceProfileValidator.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.autoconfigure.jpa; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.sql.Connection; import java.sql.SQLException; import java.util.Objects; @@ -57,7 +58,7 @@ public final class JpaDataSourceProfileValidator { try (Connection connection = dataSource.getConnection()) { versionPolicy.requireStable(connection.getMetaData()); } catch (SQLException unreachable) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "could not open a connection to validate the datasource: " + unreachable.getMessage() + ". A context that starts without this check reports healthy and fails on whoever " diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaOffAutoConfigurationImportFilter.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaOffAutoConfigurationImportFilter.java index c5e31883..e21cfb50 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/JpaOffAutoConfigurationImportFilter.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/JpaOffAutoConfigurationImportFilter.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.autoconfigure.persistencejpa; +package dev.caskeleton.bootstrap.autoconfigure.jpa; import java.util.Set; import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PersistenceJpaRootAutoConfiguration.java similarity index 95% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PersistenceJpaRootAutoConfiguration.java index dc8bfd30..66ac96aa 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/PersistenceJpaRootAutoConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PersistenceJpaRootAutoConfiguration.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.autoconfigure.persistencejpa; +package dev.caskeleton.bootstrap.autoconfigure.jpa; import dev.caskeleton.adapter.outbound.persistence.config.JpaAdapterComponentsConfig; import dev.caskeleton.adapter.outbound.persistence.config.PersistenceVendorSettings; @@ -6,9 +6,6 @@ import dev.caskeleton.adapter.outbound.persistence.fileserver.FileserverJpaPersi import dev.caskeleton.adapter.outbound.persistence.h2.H2PersistenceConfig; import dev.caskeleton.adapter.outbound.persistence.notification.configuration.NotificationJpaPersistenceFacade; import dev.caskeleton.adapter.outbound.persistence.postgresql.PostgreSqlPersistenceConfig; -import dev.caskeleton.bootstrap.autoconfigure.jpa.JpaDataSourceProfileValidator; -import dev.caskeleton.bootstrap.autoconfigure.jpa.JpaPlatformRuntimeAutoConfiguration; -import dev.caskeleton.bootstrap.autoconfigure.jpa.PostgreSqlVersionPolicy; import dev.caskeleton.bootstrap.runtime.startup.MigrationStartupConfig; import javax.sql.DataSource; import org.springframework.beans.factory.InitializingBean; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java index 94d59707..940d849d 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/jpa/PostgreSqlVersionPolicy.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.autoconfigure.jpa; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.sql.DatabaseMetaData; import java.sql.SQLException; import java.util.Set; @@ -29,7 +30,7 @@ public final class PostgreSqlVersionPolicy { String product = metadata.getDatabaseProductName(); int major = metadata.getDatabaseMajorVersion(); if (!POSTGRESQL.equals(product) || !STABLE.contains(major)) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "Stable JPA profile requires PostgreSQL 16, 17 or 18, but the datasource reported " + product + ' ' diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/DistributedLockConfig.java similarity index 96% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/DistributedLockConfig.java index 5ae09f17..ac962913 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/DistributedLockConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/DistributedLockConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.lock; +package dev.caskeleton.bootstrap.autoconfigure.lock; import dev.caskeleton.application.lock.DistributedLockPort; import io.micrometer.core.instrument.MeterRegistry; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPort.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPort.java similarity index 91% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPort.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPort.java index 6ec2bd1b..ba135f98 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPort.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPort.java @@ -1,8 +1,9 @@ -package dev.caskeleton.bootstrap.lock; +package dev.caskeleton.bootstrap.autoconfigure.lock; import dev.caskeleton.application.lock.DistributedLock; import dev.caskeleton.application.lock.DistributedLockPort; import dev.caskeleton.application.lock.LockAcquisitionTimeoutException; +import dev.caskeleton.bootstrap.runtime.RuntimeWarnings; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import java.time.Duration; @@ -78,11 +79,8 @@ public final class MeteredDistributedLockPort implements DistributedLockPort { try { Counter.builder(LOCK_ACQUISITION).tag(TAG_OUTCOME, outcome).register(registry).increment(); } catch (RuntimeException ex) { - log.warn( - "distributed-lock metrics: failed to record counter {}[outcome={}]", - LOCK_ACQUISITION, - outcome, - ex); + RuntimeWarnings.meterUnavailable( + "distributed-lock", LOCK_ACQUISITION + "[outcome=" + outcome + "]", ex); } } @@ -93,7 +91,7 @@ public final class MeteredDistributedLockPort implements DistributedLockPort { try { Counter.builder(LOCK_LEASE_EXPIRED).register(registry).increment(); } catch (RuntimeException ex) { - log.warn("distributed-lock metrics: failed to record counter {}", LOCK_LEASE_EXPIRED, ex); + RuntimeWarnings.meterUnavailable("distributed-lock", LOCK_LEASE_EXPIRED, ex); } } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthConfig.java similarity index 96% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthConfig.java index 0b90a579..e05bd94d 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.mongo; +package dev.caskeleton.bootstrap.autoconfigure.mongo; import dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformHealthIndicator; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributor.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributor.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributor.java index 50bd9417..c5136913 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributor.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributor.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.mongo; +package dev.caskeleton.bootstrap.autoconfigure.mongo; import dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformHealthIndicator; import java.util.Objects; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformCodecConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformCodecConfig.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformCodecConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformCodecConfig.java index b121b53b..633ed21c 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformCodecConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformCodecConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.JacksonRoutingPlanCodec; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformDispatchConfig.java similarity index 99% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformDispatchConfig.java index 3fe9d35b..0e23488f 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformDispatchConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformDispatchConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.NotificationDispatchProperties; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformObservabilityConfig.java similarity index 93% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformObservabilityConfig.java index 3933f2ad..ca081a5c 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformObservabilityConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformObservabilityConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationProviderAssembly; @@ -6,6 +6,7 @@ import dev.caskeleton.adapter.outbound.notification.platform.dispatch.LoggingInb import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderRuntimeRegistry; import dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotificationAudit; import dev.caskeleton.adapter.outbound.notification.platform.observation.LoggingNotificationMetrics; +import dev.caskeleton.adapter.outbound.notification.platform.observation.MicrometerNotificationMetrics; import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter; import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationServingThresholds; import dev.caskeleton.application.notification.platform.api.ProviderProfileId; @@ -15,9 +16,8 @@ import dev.caskeleton.application.notification.platform.observation.Notification import dev.caskeleton.application.notification.platform.observation.NotificationMetricsPort; import dev.caskeleton.application.notification.platform.observation.NotificationSecurityAuditPort; import dev.caskeleton.application.notification.platform.observation.NotificationServingStatePort; -import dev.caskeleton.bootstrap.notification.observation.MicrometerNotificationMetrics; -import dev.caskeleton.bootstrap.notification.observation.NotificationHealthEndpoint; -import dev.caskeleton.bootstrap.notification.observation.NotificationObservationConvention; +import dev.caskeleton.bootstrap.autoconfigure.notification.observation.NotificationHealthEndpoint; +import dev.caskeleton.bootstrap.autoconfigure.notification.observation.NotificationObservationConvention; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformPolicyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformPolicyConfig.java similarity index 99% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformPolicyConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformPolicyConfig.java index d1f963b8..0273a6ac 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformPolicyConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformPolicyConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.application.notification.platform.dispatch.CanonicalNotificationPlanWriter; import dev.caskeleton.application.notification.platform.dispatch.MessageDigestPort; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformProviderConfig.java similarity index 99% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformProviderConfig.java index fa5038a2..45b0445a 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformProviderConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformProviderConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationProviderAssembly; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRegistriesConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRegistriesConfig.java similarity index 73% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRegistriesConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRegistriesConfig.java index 3fc38021..7fe7d62d 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRegistriesConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRegistriesConfig.java @@ -1,6 +1,8 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; +import dev.caskeleton.adapter.outbound.notification.platform.callback.MapProviderCallbackAdapterRegistry; +import dev.caskeleton.adapter.outbound.notification.platform.callback.MapProviderEventProjectorRegistry; import dev.caskeleton.application.notification.platform.api.ProviderId; import dev.caskeleton.application.notification.platform.api.ProviderProfileId; import dev.caskeleton.application.notification.platform.callback.CallbackLimits; @@ -11,8 +13,6 @@ import dev.caskeleton.application.notification.platform.callback.ProviderEventPr import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.Optional; import java.util.Set; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; @@ -38,8 +38,7 @@ public class NotificationPlatformRegistriesConfig { List projectors) { Map byProvider = new HashMap<>(); projectors.forEach(projector -> byProvider.put(projector.providerId(), projector)); - Map frozen = Map.copyOf(byProvider); - return providerId -> Optional.ofNullable(frozen.get(providerId)); + return new MapProviderEventProjectorRegistry(byProvider); } /** Callback adapter lookup by configured profile. */ @@ -61,30 +60,11 @@ public class NotificationPlatformRegistriesConfig { } }); - Map frozen = Map.copyOf(byProfile); CallbackLimits limits = new CallbackLimits( settings.callbacks().maxBodyBytes(), Set.of("application/json", "text/plain", "application/x-www-form-urlencoded")); - return new ProviderCallbackAdapterRegistry() { - - @Override - public ProviderCallbackAdapter require(ProviderProfileId profileId) { - Objects.requireNonNull(profileId, "profileId"); - ProviderCallbackAdapter adapter = frozen.get(profileId); - if (adapter == null) { - // Refusing an unknown profile is a security property: an endpoint that accepted any - // profile name would let a caller pick which signature check runs. - throw new IllegalStateException("no callback adapter registered for the profile"); - } - return adapter; - } - - @Override - public CallbackLimits limitsFor(ProviderProfileId profileId) { - return limits; - } - }; + return new MapProviderCallbackAdapterRegistry(byProfile, limits); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRuntimeConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRuntimeConfig.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRuntimeConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRuntimeConfig.java index cbbb8bd8..f1899231 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformRuntimeConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformRuntimeConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformSecretsConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformSecretsConfig.java similarity index 96% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformSecretsConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformSecretsConfig.java index ac69e3b7..b5fcab98 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformSecretsConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformSecretsConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationSecretRequirements; @@ -6,6 +6,7 @@ import dev.caskeleton.adapter.outbound.notification.platform.security.SettingsSe import dev.caskeleton.application.notification.platform.security.SecretKeyMaterial; import dev.caskeleton.application.notification.platform.security.SecretMaterialProvider; import dev.caskeleton.application.notification.platform.security.SecretPurpose; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.util.Base64; import java.util.EnumMap; import java.util.Map; @@ -141,7 +142,7 @@ public class NotificationPlatformSecretsConfig { boolean supplied = base64 != null && !base64.isBlank(); if (!supplied) { if (required.contains(purpose)) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "notification secret for " + purpose + " is not configured; the capability that uses it is enabled, so the platform" @@ -151,7 +152,7 @@ public class NotificationPlatformSecretsConfig { } String keyId = settings.activeKeyIds().get(purpose.name()); if (keyId == null || keyId.isBlank()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "no active key id configured for " + purpose + "; a constant id makes a rotation indistinguishable from the key it replaced, so" @@ -181,13 +182,13 @@ public class NotificationPlatformSecretsConfig { if (settings.activeKeyIds().containsValue(keyId)) { // The same id under two different materials is the one state no reader can // resolve: the envelope names an id and two keys answer to it. - throw new IllegalStateException( + throw StartupFailures.envValidation( "key id " + keyId + " is both active and historical"); } SecretKeyMaterial previous = historical.put(keyId, material(purpose, keyId, base64)); if (previous != null) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "key id " + keyId + " is declared for both " @@ -203,12 +204,12 @@ public class NotificationPlatformSecretsConfig { /** Decodes and length-checks one key, active or retired. */ private static SecretKeyMaterial material(SecretPurpose purpose, String keyId, String base64) { if (base64 == null || base64.isBlank()) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "notification secret " + keyId + " for " + purpose + " has no value"); } byte[] material = decode(purpose, keyId, base64); if (material.length < MINIMUM_KEY_BYTES) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "notification secret " + keyId + " for " @@ -229,7 +230,7 @@ public class NotificationPlatformSecretsConfig { } catch (IllegalArgumentException unknown) { // Naming the purpose, never the keys under it: a typo here would otherwise be reported by // echoing the block that was misfiled. - throw new IllegalStateException( + throw StartupFailures.envValidation( "historical notification secrets are declared under key purpose " + name + ", which is not a purpose this platform issues keys for", @@ -243,7 +244,7 @@ public class NotificationPlatformSecretsConfig { } catch (IllegalArgumentException malformed) { // The id and the purpose, never the value, and never the decoder's own message — a base64 // failure report can quote the input it choked on. - throw new IllegalStateException( + throw StartupFailures.envValidation( "notification secret " + keyId + " for " + purpose + " is not valid base64"); } } @@ -267,7 +268,7 @@ public class NotificationPlatformSecretsConfig { String fingerprint = java.util.HexFormat.of().formatHex(key.material()); SecretKeyMaterial previous = seen.put(fingerprint, key); if (previous != null) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "notification secrets " + previous.keyId() + " (" diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformWorkerConfig.java similarity index 99% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformWorkerConfig.java index 01cd5fb0..de247b65 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformWorkerConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.LeaseRecoveryService; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.NotificationBackgroundWorkers; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationRootAutoConfiguration.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationRootAutoConfiguration.java index abdc586e..cd152a3c 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationRootAutoConfiguration.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import dev.caskeleton.adapter.outbound.notification.NotificationConfig; import dev.caskeleton.adapter.outbound.notification.NotificationRoutesSettings; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationHealthEndpoint.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationHealthEndpoint.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationHealthEndpoint.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationHealthEndpoint.java index 37e49612..889be11a 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationHealthEndpoint.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationHealthEndpoint.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification.observation; +package dev.caskeleton.bootstrap.autoconfigure.notification.observation; import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter; import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthSnapshot; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationContext.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationContext.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationContext.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationContext.java index fe7fcb70..064db3cf 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationContext.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationContext.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification.observation; +package dev.caskeleton.bootstrap.autoconfigure.notification.observation; import io.micrometer.observation.Observation; import java.util.LinkedHashMap; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationConvention.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationConvention.java index d3df92bd..44a1da60 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationConvention.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationConvention.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification.observation; +package dev.caskeleton.bootstrap.autoconfigure.notification.observation; import dev.caskeleton.application.notification.platform.observation.CardinalityGuard; import io.micrometer.common.KeyValue; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfig.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfig.java index 5983fdb7..8b14c416 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import dev.caskeleton.application.outbox.OutboxBackoffPolicy; import dev.caskeleton.application.outbox.OutboxMessagePublishPort; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxLeaderElectionToken.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxLeaderElectionToken.java similarity index 92% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxLeaderElectionToken.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxLeaderElectionToken.java index 5d79de32..35c167d1 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxLeaderElectionToken.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxLeaderElectionToken.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; /** * Marker bean: presence in the context satisfies the multi-instance coordination requirement (no diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxMetrics.java similarity index 89% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxMetrics.java index cba18518..08bc1e8b 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxMetrics.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxMetrics.java @@ -1,8 +1,9 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import dev.caskeleton.application.outbox.OutboxEventStatus; import dev.caskeleton.application.outbox.OutboxRelayResult; import dev.caskeleton.application.outbox.OutboxStorePort; +import dev.caskeleton.bootstrap.runtime.RuntimeWarnings; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MultiGauge; @@ -11,8 +12,6 @@ import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; /** @@ -21,8 +20,6 @@ import org.springframework.beans.factory.ObjectProvider; */ public final class OutboxMetrics { - private static final Logger log = LoggerFactory.getLogger(OutboxMetrics.class); - private static final String COUNTER_NAME = "outbox.publisher.published.total"; private static final String SIZE_GAUGE = "outbox.pending.size"; private static final String LAG_GAUGE = "outbox.publisher.lag"; @@ -65,10 +62,9 @@ public final class OutboxMetrics { .register(registry) .increment(); } catch (Exception ex) { - log.warn( - "outbox metrics: failed to record counter for eventType={} outcome={}", - eo.eventType(), - eo.outcome(), + RuntimeWarnings.meterUnavailable( + "outbox", + COUNTER_NAME + "[eventType=" + eo.eventType() + ",outcome=" + eo.outcome() + "]", ex); } } @@ -98,7 +94,7 @@ public final class OutboxMetrics { } pendingSizeGauge.register(rows, true); } catch (Exception ex) { - log.warn("outbox metrics: failed to refresh pending-size gauge", ex); + RuntimeWarnings.meterUnavailable("outbox", "pending-size gauge", ex); } } @@ -111,7 +107,7 @@ public final class OutboxMetrics { } publisherLagGauge.register(rows, true); } catch (Exception ex) { - log.warn("outbox metrics: failed to refresh publisher-lag gauge", ex); + RuntimeWarnings.meterUnavailable("outbox", "publisher-lag gauge", ex); } } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidator.java similarity index 93% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidator.java index 83f20605..e2227b31 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidator.java @@ -1,5 +1,6 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.util.Objects; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.env.Environment; @@ -51,7 +52,7 @@ public final class OutboxRelayBrokerRequirementValidator implements Initializing if (!relayEnabled || !broker.isBlank()) { return; } - throw new IllegalStateException( + throw StartupFailures.requiredAdapterDisabled( RELAY_KEY + " is enabled but " + BROKER_KEY diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayScheduler.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayScheduler.java index 233d661d..5985272f 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxRelayScheduler.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayScheduler.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import dev.caskeleton.application.outbox.OutboxRelayResult; import dev.caskeleton.application.outbox.PublishPendingOutboxEventsCommand; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettings.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxSettings.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettings.java index 01899318..f12a4eca 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/outbox/OutboxSettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettings.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.time.Duration; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidator.java similarity index 73% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidator.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidator.java index 96b7675d..5b21d899 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidator.java @@ -1,8 +1,8 @@ -package dev.caskeleton.bootstrap.runtime; +package dev.caskeleton.bootstrap.autoconfigure.redis; -import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; -import java.util.ArrayList; -import java.util.List; +import dev.caskeleton.bootstrap.runtime.SecretSourceValidator; +import dev.caskeleton.bootstrap.runtime.startup.StartupPhase; +import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext; import java.util.Map; import java.util.Objects; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -43,25 +43,23 @@ public class RedisActivationValidator implements SmartInitializingSingleton { if (isRedisGloballyEnabled()) { return; } - List contradictions = new ArrayList<>(); + StartupValidationContext contradictions = + StartupValidationContext.forPhase(StartupPhase.ADAPTER_ENABLEMENT); REDIS_SELECTING_VALUES.forEach( (property, selectingValue) -> { String configured = environment.getProperty(property); if (selectingValue.equalsIgnoreCase(configured)) { - contradictions.add(property + "=" + configured); + contradictions.reject(property + "=" + configured); } }); - if (contradictions.isEmpty()) { - return; - } - contradictions.sort(String::compareTo); - throw StartupFailures.requiredAdapterDisabled( - "Redis is disabled (" - + SecretSourceValidator.REDIS_ENABLED_PROPERTY - + "=false, env APP_REDIS_ENABLED) but these roles select it: " - + contradictions - + " — set APP_REDIS_ENABLED=true to compose Redis, or point each role at a " - + "non-Redis provider. A role selector never activates Redis on its own."); + contradictions + .sorted() + .failIfViolated( + "Redis is disabled (" + + SecretSourceValidator.REDIS_ENABLED_PROPERTY + + "=false, env APP_REDIS_ENABLED) but these roles select it", + "set APP_REDIS_ENABLED=true to compose Redis, or point each role at a non-Redis " + + "provider. A role selector never activates Redis on its own."); } private boolean isRedisGloballyEnabled() { diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java new file mode 100644 index 00000000..91c76e99 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCacheConfig.java @@ -0,0 +1,60 @@ +package dev.caskeleton.bootstrap.autoconfigure.redis; + +import dev.caskeleton.adapter.outbound.cache.redis.cache.RedisCacheRegionAdapter; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner; +import dev.caskeleton.application.cache.CacheRegionPort; +import dev.caskeleton.bootstrap.runtime.SecretSource; +import java.time.Clock; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * The Redis-backed semantic cache region. + * + *

One capability, one configuration class (BOOT-007). It contributes nothing unless the cache + * binding names Redis, and it is only reachable at all while the Redis master switch is on, because + * {@link RedisCapabilityConfig} imports it and carries that switch. + */ +@Configuration(proxyBeanMethods = false) +class RedisCacheConfig { + + /** + * The semantic cache region for the default binding. + * + * @param owner the Redis runtime owner + * @param sdk the Redis SDK settings, which own the namespace + * @param capabilities the capability settings + * @param secretSource resolves the key-digest material + * @param clock the clock expiries are measured against + * @return the cache region port + */ + @Bean + @ConditionalOnProperty( + name = "ca-skeleton.capabilities.cache.bindings.default", + havingValue = "redis") + CacheRegionPort redisDefaultCacheRegion( + RedisRuntimeOwner owner, + RedisSdkSettings sdk, + RedisCapabilitySettings capabilities, + SecretSource secretSource, + Clock clock) { + RedisCapabilitySettings.Cache cache = capabilities.getCache(); + cache.validate(); + RedisKeys.KeyDigest digest = + RedisKeys.KeyDigest.of(secretSource, cache.getKeyHmacSecretReference(), "cache", sdk); + return new RedisCacheRegionAdapter<>( + owner, + new RedisCacheRegionAdapter.CacheKeys( + RedisKeys.namespaceOf(sdk), cache.getSemanticRegion(), cache.getKeyVersion()), + digest::of, + value -> value, + stored -> stored, + clock, + cache.getPositiveSoftTtl(), + cache.getPositiveHardTtl(), + cache.getNegativeTtl(), + cache.getCommandTimeout()); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java new file mode 100644 index 00000000..14a1cd35 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityConfig.java @@ -0,0 +1,68 @@ +package dev.caskeleton.bootstrap.autoconfigure.redis; + +import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkAutoConfiguration; +import dev.caskeleton.bootstrap.runtime.SecretSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +/** + * Composes the Redis semantic ports the deployment's role selectors asked for. + * + *

Before this existed, {@code APP_REDIS_ENABLED=true} produced a client, a connection owner and + * a health contributor — and nothing else. Every semantic port stayed unimplemented, so a + * deployment that selected {@code redis} for its rate limiter got a context that started, reported + * healthy, and had no rate limiter. "Redis is reachable" is not the same claim as "the capabilities + * that need Redis are available", and only the second one is worth making. + * + *

Each port is conditional on its own selector, so selecting Redis for leases does not build an + * idempotency store nobody asked for. All of them are conditional on {@link RedisRuntimeOwner}, + * which exists only while the global switch is on — a role selector never activates Redis by + * itself, and {@code RedisActivationValidator} refuses the contradiction of a role that selects + * Redis while the switch is off. + * + *

This class is the composition point only: it holds the master switch, binds the settings once, + * bridges the secret backend into the SDK, and imports one configuration per capability. It used to + * hold all four capabilities plus rate-limit policy compilation and the key digest — 376 lines, in + * a leaf whose Notification platform had already been split into nine focused configurations. The + * decomposition pattern existed; this was simply where it had not been applied (BOOT-007). + * + *

The namespace comes from {@code app.redis.namespace} for every capability. That is the change + * that makes the ACL pattern meaningful: one prefix, {@code {environment}:{service}:{domain}}, that + * the deployment's {@code ~} pattern can actually fence. + */ +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(RedisCapabilitySettings.class) +// The global switch, not a role selector. A role selector says which capability composes; it +// never says Redis exists, and RedisActivationValidator refuses the contradiction of one +// without the other. +@ConditionalOnProperty(prefix = "app.redis", name = "enabled", havingValue = "true") +// Imported rather than scanned: the master switch above gates this class, and an @Import only +// registers what a registered configuration asked for. Component scanning would have made each +// capability configuration independently discoverable and the switch advisory. +@Import({ + RedisCacheConfig.class, + RedisRateLimitConfig.class, + RedisLeaseConfig.class, + RedisIdempotencyConfig.class +}) +public class RedisCapabilityConfig { + + /** + * Bridges the deployment's configured secret backend into the Redis SDK. + * + *

Without it the SDK falls back to reading the process environment directly, which quietly + * bypasses whichever backend {@code ca-skeleton.secret-source.strategy} selected — so a + * deployment using a secret manager would resolve its Redis credential from an environment + * variable that is not supposed to hold it. + * + * @param secretSource the configured backend + * @return the SDK's view of it + */ + @Bean + RedisSdkAutoConfiguration.RedisSecretSource redisSdkSecretSource(SecretSource secretSource) { + return secretSource::resolve; + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilitySettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilitySettings.java similarity index 97% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilitySettings.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilitySettings.java index 2de8d527..068a263f 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilitySettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilitySettings.java @@ -1,5 +1,6 @@ -package dev.caskeleton.bootstrap.redis; +package dev.caskeleton.bootstrap.autoconfigure.redis; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.time.Duration; import java.util.LinkedHashMap; import java.util.Map; @@ -69,7 +70,7 @@ public class RedisCapabilitySettings { void validate() { if (positiveSoftTtl.compareTo(positiveHardTtl) > 0) { - throw new IllegalStateException( + throw StartupFailures.envValidation( "the cache soft TTL must not exceed the hard TTL: an entry cannot stop being fresh" + " after it has stopped being usable"); } @@ -77,17 +78,17 @@ public class RedisCapabilitySettings { // The floor exists so an operator cannot configure a cache whose entries expire faster // than the round trip that wrote them, which costs a Redis write per read and returns a // miss every time. - throw new IllegalStateException( + throw StartupFailures.envValidation( "the cache hard TTL of " + positiveHardTtl + " is below the configured minimum of " + minimumHardTtl); } if (commandTimeout.isZero() || commandTimeout.isNegative()) { - throw new IllegalStateException("the cache command timeout must be positive"); + throw StartupFailures.envValidation("the cache command timeout must be positive"); } if (keyVersion < 1) { - throw new IllegalStateException("the cache key version must be positive"); + throw StartupFailures.envValidation("the cache key version must be positive"); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java new file mode 100644 index 00000000..f94def89 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisIdempotencyConfig.java @@ -0,0 +1,79 @@ +package dev.caskeleton.bootstrap.autoconfigure.redis; + +import dev.caskeleton.adapter.outbound.cache.redis.idempotency.IdempotencyScripts; +import dev.caskeleton.adapter.outbound.cache.redis.idempotency.RedisIdempotencyStoreAdapter; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner; +import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2; +import dev.caskeleton.application.idempotency.v2.IdempotencyStorePortV2; +import java.time.Clock; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * The Redis-backed owner-safe idempotency store and the executor that drives it. + * + *

Both beans stay together because the selection guard in {@code + * IdempotencyProviderSelectionConfig} refuses a store with no executor: they are one selection, not + * two (BOOT-007). + */ +@Configuration(proxyBeanMethods = false) +class RedisIdempotencyConfig { + + /** + * The owner-safe request-replay store. + * + * @param owner the Redis runtime owner + * @param sdk the Redis SDK settings, which own the namespace + * @param capabilities the capability settings + * @param clock the clock lease deadlines are measured against + * @return the V2 store port + */ + @Bean + @ConditionalOnProperty( + name = "ca-skeleton.capabilities.idempotency.provider", + havingValue = "redis") + IdempotencyStorePortV2 redisIdempotencyStore( + RedisRuntimeOwner owner, + RedisSdkSettings sdk, + RedisCapabilitySettings capabilities, + Clock clock) { + RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency(); + return new RedisIdempotencyStoreAdapter( + owner, + new RedisIdempotencyStoreAdapter.IdempotencyKeys( + RedisKeys.namespaceOf(sdk), idempotency.getKeyVersion()), + new IdempotencyScripts(), + clock, + idempotency.getCommandTimeout()); + } + + /** + * The request-replay lifecycle driven over the selected owner-safe store. + * + *

Provider-neutral by type — it depends on {@link IdempotencyStorePortV2}, not on Redis — and + * composed here because this is where the settings behind it are bound. A selection that produced + * the store and no executor produced a store nothing could drive, which is what {@code + * IdempotencyProviderSelectionConfig} refuses. + * + * @param store the selected owner-safe store + * @param capabilities the capability settings + * @return the owner-safe request-replay executor + */ + @Bean + @ConditionalOnProperty( + name = "ca-skeleton.capabilities.idempotency.provider", + havingValue = "redis") + IdempotencyExecutorV2 idempotencyExecutorV2( + IdempotencyStorePortV2 store, RedisCapabilitySettings capabilities) { + RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency(); + return new IdempotencyExecutorV2( + store, + idempotency.getProcessingLease(), + idempotency.getReplayTtl(), + idempotency.getFailureRetention(), + idempotency.getResponseCodecId(), + idempotency.getPolicyRevision()); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java new file mode 100644 index 00000000..19aa3c25 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisKeys.java @@ -0,0 +1,103 @@ +package dev.caskeleton.bootstrap.autoconfigure.redis; + +import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisNamespace; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings; +import dev.caskeleton.bootstrap.runtime.SecretSource; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import java.nio.charset.StandardCharsets; + +/** + * Namespace construction and the keyed digest every Redis capability derives its key material from. + * + *

Both were private members of the single Redis configuration class. Splitting that class by + * capability (BOOT-007) made them shared, and shared members of a class that no longer exists have + * to live somewhere: here, where the two things a Redis key is made of — the deployment's namespace + * and the digest that hides identifiers inside it — sit together and are named for what they are. + */ +final class RedisKeys { + + private RedisKeys() {} + + /** The deployment's key namespace: {@code {environment}:{service}:{domain}}. */ + static RedisNamespace namespaceOf(RedisSdkSettings settings) { + return new RedisNamespace( + settings.getNamespace().getEnvironment(), + settings.getNamespace().getService(), + settings.getNamespace().getDomain()); + } + + /** + * Digests a semantic key before it reaches Redis. + * + *

Keyed, not plain. An unkeyed digest of a low-entropy identifier — a customer number, a short + * order id — is reversible by anyone who can read the keyspace and can guess the format, which is + * everyone who can run {@code SCAN}. The key comes from the deployment's secret backend, so the + * keyspace is only correlatable by something that already holds the secret. + */ + record KeyDigest(javax.crypto.Mac prototype) { + + static KeyDigest of( + SecretSource secretSource, String reference, String capability, RedisSdkSettings sdk) { + String name = secretName(reference, capability); + String material = + secretSource + .resolve(name) + .orElseThrow( + () -> + StartupFailures.envValidation( + "the Redis " + + capability + + " key-digest secret '" + + name + + "' resolved to nothing. Without it the keyspace would carry" + + " identifiers in a form anybody who can read it could reverse.")); + try { + javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256"); + mac.init( + new javax.crypto.spec.SecretKeySpec( + // Namespace-bound: the same identifier in staging and production must not digest + // to the same key, or a keyspace dump from the softer environment would map + // one-for-one onto the harder one. + (material + + '|' + + sdk.getNamespace().getEnvironment() + + ':' + + sdk.getNamespace().getService() + + ':' + + sdk.getNamespace().getDomain()) + .getBytes(StandardCharsets.UTF_8), + "HmacSHA256")); + return new KeyDigest(mac); + } catch (java.security.GeneralSecurityException failure) { + throw new IllegalStateException("HmacSHA256 must be available", failure); + } + } + + private static String secretName(String reference, String capability) { + if (reference == null || reference.isBlank()) { + throw StartupFailures.envValidation( + "the Redis " + capability + " capability needs a key-digest secret reference"); + } + int separator = reference.lastIndexOf('/'); + if (!reference.startsWith("secret://") || separator < 0) { + throw StartupFailures.envValidation( + "the Redis " + + capability + + " key-digest reference '" + + reference + + "' must be secret:///"); + } + return reference.substring(separator + 1); + } + + String of(String key) { + try { + javax.crypto.Mac mac = (javax.crypto.Mac) prototype.clone(); + return "hv1:" + + java.util.HexFormat.of().formatHex(mac.doFinal(key.getBytes(StandardCharsets.UTF_8))); + } catch (CloneNotSupportedException failure) { + throw new IllegalStateException("a Mac must be cloneable", failure); + } + } + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java new file mode 100644 index 00000000..a3f22457 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisLeaseConfig.java @@ -0,0 +1,45 @@ +package dev.caskeleton.bootstrap.autoconfigure.redis; + +import dev.caskeleton.adapter.outbound.cache.redis.lease.LeaseScripts; +import dev.caskeleton.adapter.outbound.cache.redis.lease.RedisDistributedLeaseAdapter; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner; +import dev.caskeleton.application.lease.DistributedLeasePort; +import java.time.Clock; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** The Redis-backed distributed lease. Efficiency only — it never supplies fencing. */ +@Configuration(proxyBeanMethods = false) +class RedisLeaseConfig { + + /** + * The distributed lease port. Efficiency only — it never supplies fencing. + * + * @param owner the Redis runtime owner + * @param sdk the Redis SDK settings, which own the namespace + * @param capabilities the capability settings + * @param clock the wall clock used for reporting instants + * @return the lease port + */ + @Bean + @ConditionalOnProperty(name = "ca-skeleton.capabilities.lease.provider", havingValue = "redis") + DistributedLeasePort redisDistributedLeasePort( + RedisRuntimeOwner owner, + RedisSdkSettings sdk, + RedisCapabilitySettings capabilities, + Clock clock) { + RedisCapabilitySettings.Lease lease = capabilities.getLease(); + return new RedisDistributedLeaseAdapter( + owner, + new RedisDistributedLeaseAdapter.LeaseKeys( + RedisKeys.namespaceOf(sdk), lease.getKeyVersion()), + new LeaseScripts(), + clock, + System::nanoTime, + lease.getCommandTimeout(), + lease.getContentionRetryAfter(), + lease.getDriftBudget()); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java new file mode 100644 index 00000000..e7905454 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisRateLimitConfig.java @@ -0,0 +1,133 @@ +package dev.caskeleton.bootstrap.autoconfigure.redis; + +import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitKeys; +import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitScripts; +import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RedisEdgeRateLimitAdapter; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings; +import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner; +import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.shared.ratelimit.EdgeRateLimitPort; +import dev.caskeleton.shared.ratelimit.RateLimitAlgorithm; +import dev.caskeleton.shared.ratelimit.RateLimitFailurePolicy; +import dev.caskeleton.shared.ratelimit.RateLimitPolicy; +import dev.caskeleton.shared.ratelimit.RateParameters; +import java.time.Clock; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * The Redis-backed edge rate limiter, and the policy compilation it needs. + * + *

The two private helpers below are the largest part of what used to make the single Redis + * configuration class 376 lines: policy compilation belongs to the rate limiter and to nothing + * else, so it moves with it (BOOT-007). + */ +@Configuration(proxyBeanMethods = false) +class RedisRateLimitConfig { + + /** + * The provider-neutral edge rate limiter, backed by Redis. + * + * @param owner the Redis runtime owner + * @param sdk the Redis SDK settings, which own the namespace + * @param capabilities the capability settings + * @param clock the clock decision windows are measured against + * @return the rate limit port + */ + @Bean + @ConditionalOnProperty( + name = "ca-skeleton.capabilities.rate-limit.provider", + havingValue = "redis") + EdgeRateLimitPort redisEdgeRateLimitPort( + RedisRuntimeOwner owner, + RedisSdkSettings sdk, + RedisCapabilitySettings capabilities, + Clock clock) { + RedisCapabilitySettings.RateLimit rateLimit = capabilities.getRateLimit(); + return new RedisEdgeRateLimitAdapter( + owner, + new RateLimitKeys(RedisKeys.namespaceOf(sdk), rateLimit.getKeyVersion()), + policiesOf(rateLimit), + new RateLimitScripts(), + clock, + rateLimit.getCommandTimeout(), + rateLimit.getFailureRetryAfter()); + } + + private static Map policiesOf( + RedisCapabilitySettings.RateLimit rateLimit) { + if (rateLimit.getPolicies().isEmpty()) { + // An enabled limiter with no policy would answer every request "unknown policy", which the + // port reports as a deployment error on every call rather than once at startup. + throw StartupFailures.envValidation( + "the Redis rate limiter is selected but no policy is configured under" + + " ca-skeleton.capabilities.rate-limit.policies"); + } + Map policies = new LinkedHashMap<>(); + rateLimit + .getPolicies() + .forEach((id, policy) -> policies.put(id, policyOf(id, policy, rateLimit))); + if (!policies.containsKey(rateLimit.getDefaultPolicyId())) { + throw StartupFailures.envValidation( + "the default rate-limit policy '" + + rateLimit.getDefaultPolicyId() + + "' is not among the configured policies " + + policies.keySet()); + } + return policies; + } + + private static RateLimitPolicy policyOf( + String id, + RedisCapabilitySettings.RateLimit.Policy policy, + RedisCapabilitySettings.RateLimit rateLimit) { + RateLimitAlgorithm algorithm = + switch (policy.getAlgorithm().toLowerCase(Locale.ROOT)) { + case "fixed-window" -> RateLimitAlgorithm.FIXED_WINDOW; + case "sliding-counter" -> RateLimitAlgorithm.SLIDING_COUNTER; + case "token-bucket" -> RateLimitAlgorithm.TOKEN_BUCKET; + default -> + throw StartupFailures.envValidation( + "unsupported rate-limit algorithm '" + policy.getAlgorithm() + "' for " + id); + }; + RateParameters parameters = + switch (algorithm) { + case FIXED_WINDOW -> + new RateParameters.FixedWindow(policy.getLimit(), policy.getWindow()); + case SLIDING_COUNTER -> + new RateParameters.SlidingCounter(policy.getLimit(), policy.getWindow()); + case TOKEN_BUCKET -> + new RateParameters.TokenBucket( + policy.getCapacity(), policy.getRefillTokens(), policy.getRefillPeriod()); + // No `default`. RateLimitAlgorithm has exactly these three constants, so the branch + // that + // used to be here was unreachable — and it suppressed the compiler's exhaustiveness + // check + // on this switch expression, which is the one thing that would notice a fourth + // algorithm + // being added without parameters for it (BOOT-010). + }; + if (!"fail-closed".equalsIgnoreCase(rateLimit.getFailurePolicy())) { + // The port only implements fail-closed, so a deployment that configured anything else has a + // limit it believes is permissive and is not. + throw StartupFailures.envValidation( + "only fail-closed is supported for the Redis rate limiter, but" + + " ca-skeleton.capabilities.rate-limit.failure-policy is '" + + rateLimit.getFailurePolicy() + + "'"); + } + return new RateLimitPolicy( + id, + policy.getRevision(), + algorithm, + parameters, + policy.getMaximumCost(), + policy.getCleanupGrace(), + policy.getMaximumClockRegression(), + RateLimitFailurePolicy.FAIL_CLOSED); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessor.java similarity index 69% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessor.java index 2c5d4ecb..373b5adf 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessor.java @@ -1,16 +1,13 @@ -package dev.caskeleton.bootstrap.runtime; +package dev.caskeleton.bootstrap.autoconfigure.redis; import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisCorrectnessRoles; -import java.util.Arrays; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; +import dev.caskeleton.bootstrap.runtime.ReadinessGroup; +import dev.caskeleton.bootstrap.runtime.SecretSourceValidator; +import dev.caskeleton.bootstrap.runtime.startup.StartupOrder; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.MapPropertySource; /** * Adds the Redis readiness contributor to the readiness group, and only when it will exist. @@ -35,8 +32,6 @@ import org.springframework.core.env.MapPropertySource; */ public class RedisReadinessGroupPostProcessor implements EnvironmentPostProcessor, Ordered { - static final String READINESS_INCLUDE = "management.endpoint.health.group.readiness.include"; - private static final String PROPERTY_SOURCE = "redisReadinessGroup"; @Override @@ -45,22 +40,8 @@ public class RedisReadinessGroupPostProcessor implements EnvironmentPostProcesso if (!redisEnabled(environment) || !RedisCorrectnessRoles.anySelected(environment)) { return; } - String configured = environment.getProperty(READINESS_INCLUDE, ""); - Set members = new LinkedHashSet<>(); - Arrays.stream(configured.split(",")) - .map(String::trim) - .filter(member -> !member.isEmpty()) - .forEach(members::add); - if (!members.add(RedisCorrectnessRoles.REQUIRED_HEALTH_CONTRIBUTOR)) { - // Already named explicitly. Adding a source that repeats it would be harmless but would also - // hide that the operator asked for it themselves. - return; - } - environment - .getPropertySources() - .addFirst( - new MapPropertySource( - PROPERTY_SOURCE, Map.of(READINESS_INCLUDE, String.join(",", members)))); + ReadinessGroup.include( + environment, PROPERTY_SOURCE, RedisCorrectnessRoles.REQUIRED_HEALTH_CONTRIBUTOR); } private static boolean redisEnabled(ConfigurableEnvironment environment) { @@ -73,6 +54,6 @@ public class RedisReadinessGroupPostProcessor implements EnvironmentPostProcesso // After config data: the readiness group this appends to is defined in application.yml, and // reading it before that file is loaded would append to an empty string and silently drop // readinessState and db from the group. - return ConfigDataEnvironmentPostProcessor.ORDER + 1; + return StartupOrder.READINESS_REDIS; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorder.java similarity index 92% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorder.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorder.java index fcc358c5..eb601934 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorder.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorder.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import dev.caskeleton.shared.tracing.SpanErrorRecorder; import io.micrometer.tracing.Span; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingConfig.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingConfig.java index 4cb25546..57d32136 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingConfig.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import dev.caskeleton.shared.tracing.SpanErrorRecorder; import io.micrometer.core.instrument.MeterRegistry; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolver.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolver.java similarity index 96% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolver.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolver.java index 639f43fd..8d235798 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolver.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolver.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import java.util.Locale; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessor.java similarity index 72% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessor.java index 38e25a18..bd43309f 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessor.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessor.java @@ -1,8 +1,10 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; +import dev.caskeleton.bootstrap.runtime.startup.StartupOrder; import java.util.Map; import org.springframework.boot.EnvironmentPostProcessor; import org.springframework.boot.SpringApplication; +import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.MapPropertySource; @@ -11,7 +13,7 @@ import org.springframework.core.env.MapPropertySource; * management.tracing.sampling.probability}. Registered via {@code META-INF/spring.factories}. See * README for the design rationale. */ -public class TracingSamplingEnvironmentPostProcessor implements EnvironmentPostProcessor { +public class TracingSamplingEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { private static final String NATIVE_KEY = "management.tracing.sampling.probability"; private static final String CA_SAMPLE_RATE_KEY = "ca-skeleton.tracing.sample-rate"; @@ -35,4 +37,12 @@ public class TracingSamplingEnvironmentPostProcessor implements EnvironmentPostP new MapPropertySource( "tracingSamplingBridge", Map.of(NATIVE_KEY, String.valueOf(rate)))); } + + @Override + public int getOrder() { + // Declared rather than inherited. Implementing EnvironmentPostProcessor without Ordered means + // LOWEST_PRECEDENCE by default, which is the same value three other post-processors returned + // explicitly — four things claiming the same position, none of them saying so (BOOT-005). + return StartupOrder.TRACING_SAMPLING; + } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingRateGauge.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingRateGauge.java similarity index 81% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingRateGauge.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingRateGauge.java index 31061adc..d66bd7a8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSamplingRateGauge.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingRateGauge.java @@ -1,9 +1,8 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; +import dev.caskeleton.bootstrap.runtime.RuntimeWarnings; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.beans.factory.ObjectProvider; /** @@ -13,8 +12,6 @@ import org.springframework.beans.factory.ObjectProvider; */ public final class TracingSamplingRateGauge { - private static final Logger log = LoggerFactory.getLogger(TracingSamplingRateGauge.class); - public static final String METRIC_NAME = "tracing.sampling.rate"; public static final String TAG_PROFILE = "profile"; @@ -35,7 +32,7 @@ public final class TracingSamplingRateGauge { .description("Effective distributed-tracing sample rate for the active profile") .register(registry); } catch (RuntimeException ex) { - log.warn("tracing metrics: failed to register gauge {}", METRIC_NAME, ex); + RuntimeWarnings.meterUnavailable("tracing", METRIC_NAME, ex); } } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettings.java similarity index 98% rename from src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSettings.java rename to src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettings.java index 0038e641..39f8fb6f 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/tracing/TracingSettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettings.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; import java.net.URI; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java deleted file mode 100644 index b6b61131..00000000 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderSettings.java +++ /dev/null @@ -1,30 +0,0 @@ -package dev.caskeleton.bootstrap.idempotency; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.boot.context.properties.bind.ConstructorBinding; - -/** Exact provider selector; provider precedence is never inferred from the classpath. */ -@ConfigurationProperties(prefix = "ca-skeleton.capabilities.idempotency") -public record IdempotencyProviderSettings(Provider provider) { - - @ConstructorBinding - public IdempotencyProviderSettings { - provider = provider == null ? Provider.JDBC : provider; - } - - /** - * The stores a deployment may select. - * - *

{@code POSTGRESQL} selects the owner-safe V2 store in the JPA leaf. It had no value here for - * as long as it existed, so the only way to reach it was to construct it in a test: a capability - * with an implementation, a schema stream and an integration suite that no deployment could turn - * on. A store nobody can select is not a feature, and leaving the selector out did not make the - * code any less present in the artifact. - */ - public enum Provider { - DISABLED, - JDBC, - REDIS, - POSTGRESQL - } -} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java deleted file mode 100644 index 651b61f5..00000000 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/redis/RedisCapabilityConfig.java +++ /dev/null @@ -1,376 +0,0 @@ -package dev.caskeleton.bootstrap.redis; - -import dev.caskeleton.adapter.outbound.cache.redis.cache.RedisCacheRegionAdapter; -import dev.caskeleton.adapter.outbound.cache.redis.idempotency.IdempotencyScripts; -import dev.caskeleton.adapter.outbound.cache.redis.idempotency.RedisIdempotencyStoreAdapter; -import dev.caskeleton.adapter.outbound.cache.redis.lease.LeaseScripts; -import dev.caskeleton.adapter.outbound.cache.redis.lease.RedisDistributedLeaseAdapter; -import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitKeys; -import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RateLimitScripts; -import dev.caskeleton.adapter.outbound.cache.redis.ratelimit.RedisEdgeRateLimitAdapter; -import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.key.RedisNamespace; -import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkAutoConfiguration; -import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkSettings; -import dev.caskeleton.adapter.outbound.cache.redis.sdk.lettuce.connection.RedisRuntimeOwner; -import dev.caskeleton.application.cache.CacheRegionPort; -import dev.caskeleton.application.idempotency.v2.IdempotencyExecutorV2; -import dev.caskeleton.application.idempotency.v2.IdempotencyStorePortV2; -import dev.caskeleton.application.lease.DistributedLeasePort; -import dev.caskeleton.bootstrap.runtime.SecretSource; -import dev.caskeleton.shared.ratelimit.EdgeRateLimitPort; -import dev.caskeleton.shared.ratelimit.RateLimitAlgorithm; -import dev.caskeleton.shared.ratelimit.RateLimitFailurePolicy; -import dev.caskeleton.shared.ratelimit.RateLimitPolicy; -import dev.caskeleton.shared.ratelimit.RateParameters; -import java.nio.charset.StandardCharsets; -import java.time.Clock; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.context.properties.EnableConfigurationProperties; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -/** - * Composes the Redis semantic ports the deployment's role selectors asked for. - * - *

Before this existed, {@code APP_REDIS_ENABLED=true} produced a client, a connection owner and - * a health contributor — and nothing else. Every semantic port stayed unimplemented, so a - * deployment that selected {@code redis} for its rate limiter got a context that started, reported - * healthy, and had no rate limiter. "Redis is reachable" is not the same claim as "the capabilities - * that need Redis are available", and only the second one is worth making. - * - *

Each port is conditional on its own selector, so selecting Redis for leases does not build an - * idempotency store nobody asked for. All of them are conditional on {@link RedisRuntimeOwner}, - * which exists only while the global switch is on — a role selector never activates Redis by - * itself, and {@code RedisActivationValidator} refuses the contradiction of a role that selects - * Redis while the switch is off. - * - *

The namespace comes from {@code app.redis.namespace} for every capability. That is the change - * that makes the ACL pattern meaningful: one prefix, {@code {environment}:{service}:{domain}}, that - * the deployment's {@code ~} pattern can actually fence. - */ -@Configuration(proxyBeanMethods = false) -@EnableConfigurationProperties(RedisCapabilitySettings.class) -// The global switch, not a role selector. A role selector says which capability composes; it never -// says Redis exists, and RedisActivationValidator refuses the contradiction of one without the -// other. -@ConditionalOnProperty(prefix = "app.redis", name = "enabled", havingValue = "true") -public class RedisCapabilityConfig { - - /** - * Bridges the deployment's configured secret backend into the Redis SDK. - * - *

Without it the SDK falls back to reading the process environment directly, which quietly - * bypasses whichever backend {@code ca-skeleton.secret-source.strategy} selected — so a - * deployment using a secret manager would resolve its Redis credential from an environment - * variable that is not supposed to hold it. - * - * @param secretSource the configured backend - * @return the SDK's view of it - */ - @Bean - RedisSdkAutoConfiguration.RedisSecretSource redisSdkSecretSource(SecretSource secretSource) { - return secretSource::resolve; - } - - private static RedisNamespace namespaceOf(RedisSdkSettings settings) { - return new RedisNamespace( - settings.getNamespace().getEnvironment(), - settings.getNamespace().getService(), - settings.getNamespace().getDomain()); - } - - /** - * The semantic cache region for the default binding. - * - * @param owner the Redis runtime owner - * @param sdk the Redis SDK settings, which own the namespace - * @param capabilities the capability settings - * @param secretSource resolves the key-digest material - * @param clock the clock expiries are measured against - * @return the cache region port - */ - @Bean - @ConditionalOnProperty( - name = "ca-skeleton.capabilities.cache.bindings.default", - havingValue = "redis") - CacheRegionPort redisDefaultCacheRegion( - RedisRuntimeOwner owner, - RedisSdkSettings sdk, - RedisCapabilitySettings capabilities, - SecretSource secretSource, - Clock clock) { - RedisCapabilitySettings.Cache cache = capabilities.getCache(); - cache.validate(); - KeyDigest digest = KeyDigest.of(secretSource, cache.getKeyHmacSecretReference(), "cache", sdk); - return new RedisCacheRegionAdapter<>( - owner, - new RedisCacheRegionAdapter.CacheKeys( - namespaceOf(sdk), cache.getSemanticRegion(), cache.getKeyVersion()), - digest::of, - value -> value, - stored -> stored, - clock, - cache.getPositiveSoftTtl(), - cache.getPositiveHardTtl(), - cache.getNegativeTtl(), - cache.getCommandTimeout()); - } - - /** - * The provider-neutral edge rate limiter, backed by Redis. - * - * @param owner the Redis runtime owner - * @param sdk the Redis SDK settings, which own the namespace - * @param capabilities the capability settings - * @param clock the clock decision windows are measured against - * @return the rate limit port - */ - @Bean - @ConditionalOnProperty( - name = "ca-skeleton.capabilities.rate-limit.provider", - havingValue = "redis") - EdgeRateLimitPort redisEdgeRateLimitPort( - RedisRuntimeOwner owner, - RedisSdkSettings sdk, - RedisCapabilitySettings capabilities, - Clock clock) { - RedisCapabilitySettings.RateLimit rateLimit = capabilities.getRateLimit(); - return new RedisEdgeRateLimitAdapter( - owner, - new RateLimitKeys(namespaceOf(sdk), rateLimit.getKeyVersion()), - policiesOf(rateLimit), - new RateLimitScripts(), - clock, - rateLimit.getCommandTimeout(), - rateLimit.getFailureRetryAfter()); - } - - private static Map policiesOf( - RedisCapabilitySettings.RateLimit rateLimit) { - if (rateLimit.getPolicies().isEmpty()) { - // An enabled limiter with no policy would answer every request "unknown policy", which the - // port reports as a deployment error on every call rather than once at startup. - throw new IllegalStateException( - "the Redis rate limiter is selected but no policy is configured under" - + " ca-skeleton.capabilities.rate-limit.policies"); - } - Map policies = new LinkedHashMap<>(); - rateLimit - .getPolicies() - .forEach((id, policy) -> policies.put(id, policyOf(id, policy, rateLimit))); - if (!policies.containsKey(rateLimit.getDefaultPolicyId())) { - throw new IllegalStateException( - "the default rate-limit policy '" - + rateLimit.getDefaultPolicyId() - + "' is not among the configured policies " - + policies.keySet()); - } - return policies; - } - - private static RateLimitPolicy policyOf( - String id, - RedisCapabilitySettings.RateLimit.Policy policy, - RedisCapabilitySettings.RateLimit rateLimit) { - RateLimitAlgorithm algorithm = - switch (policy.getAlgorithm().toLowerCase(Locale.ROOT)) { - case "fixed-window" -> RateLimitAlgorithm.FIXED_WINDOW; - case "sliding-counter" -> RateLimitAlgorithm.SLIDING_COUNTER; - case "token-bucket" -> RateLimitAlgorithm.TOKEN_BUCKET; - default -> - throw new IllegalStateException( - "unsupported rate-limit algorithm '" + policy.getAlgorithm() + "' for " + id); - }; - RateParameters parameters = - switch (algorithm) { - case FIXED_WINDOW -> - new RateParameters.FixedWindow(policy.getLimit(), policy.getWindow()); - case SLIDING_COUNTER -> - new RateParameters.SlidingCounter(policy.getLimit(), policy.getWindow()); - case TOKEN_BUCKET -> - new RateParameters.TokenBucket( - policy.getCapacity(), policy.getRefillTokens(), policy.getRefillPeriod()); - default -> throw new IllegalStateException("unsupported rate-limit algorithm for " + id); - }; - if (!"fail-closed".equalsIgnoreCase(rateLimit.getFailurePolicy())) { - // The port only implements fail-closed, so a deployment that configured anything else has a - // limit it believes is permissive and is not. - throw new IllegalStateException( - "only fail-closed is supported for the Redis rate limiter, but" - + " ca-skeleton.capabilities.rate-limit.failure-policy is '" - + rateLimit.getFailurePolicy() - + "'"); - } - return new RateLimitPolicy( - id, - policy.getRevision(), - algorithm, - parameters, - policy.getMaximumCost(), - policy.getCleanupGrace(), - policy.getMaximumClockRegression(), - RateLimitFailurePolicy.FAIL_CLOSED); - } - - /** - * The distributed lease port. Efficiency only — it never supplies fencing. - * - * @param owner the Redis runtime owner - * @param sdk the Redis SDK settings, which own the namespace - * @param capabilities the capability settings - * @param clock the wall clock used for reporting instants - * @return the lease port - */ - @Bean - @ConditionalOnProperty(name = "ca-skeleton.capabilities.lease.provider", havingValue = "redis") - DistributedLeasePort redisDistributedLeasePort( - RedisRuntimeOwner owner, - RedisSdkSettings sdk, - RedisCapabilitySettings capabilities, - Clock clock) { - RedisCapabilitySettings.Lease lease = capabilities.getLease(); - return new RedisDistributedLeaseAdapter( - owner, - new RedisDistributedLeaseAdapter.LeaseKeys(namespaceOf(sdk), lease.getKeyVersion()), - new LeaseScripts(), - clock, - System::nanoTime, - lease.getCommandTimeout(), - lease.getContentionRetryAfter(), - lease.getDriftBudget()); - } - - /** - * The owner-safe request-replay store. - * - * @param owner the Redis runtime owner - * @param sdk the Redis SDK settings, which own the namespace - * @param capabilities the capability settings - * @param clock the clock lease deadlines are measured against - * @return the V2 store port - */ - @Bean - @ConditionalOnProperty( - name = "ca-skeleton.capabilities.idempotency.provider", - havingValue = "redis") - IdempotencyStorePortV2 redisIdempotencyStore( - RedisRuntimeOwner owner, - RedisSdkSettings sdk, - RedisCapabilitySettings capabilities, - Clock clock) { - RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency(); - return new RedisIdempotencyStoreAdapter( - owner, - new RedisIdempotencyStoreAdapter.IdempotencyKeys( - namespaceOf(sdk), idempotency.getKeyVersion()), - new IdempotencyScripts(), - clock, - idempotency.getCommandTimeout()); - } - - /** - * The request-replay lifecycle driven over the selected owner-safe store. - * - *

Provider-neutral by type — it depends on {@link IdempotencyStorePortV2}, not on Redis — and - * composed here because this is where the settings behind it are bound. A selection that produced - * the store and no executor produced a store nothing could drive, which is what {@code - * IdempotencyProviderSelectionConfig} refuses. - * - * @param store the selected owner-safe store - * @param capabilities the capability settings - * @return the owner-safe request-replay executor - */ - @Bean - @ConditionalOnProperty( - name = "ca-skeleton.capabilities.idempotency.provider", - havingValue = "redis") - IdempotencyExecutorV2 idempotencyExecutorV2( - IdempotencyStorePortV2 store, RedisCapabilitySettings capabilities) { - RedisCapabilitySettings.Idempotency idempotency = capabilities.getIdempotency(); - return new IdempotencyExecutorV2( - store, - idempotency.getProcessingLease(), - idempotency.getReplayTtl(), - idempotency.getFailureRetention(), - idempotency.getResponseCodecId(), - idempotency.getPolicyRevision()); - } - - /** - * Digests a semantic key before it reaches Redis. - * - *

Keyed, not plain. An unkeyed digest of a low-entropy identifier — a customer number, a short - * order id — is reversible by anyone who can read the keyspace and can guess the format, which is - * everyone who can run {@code SCAN}. The key comes from the deployment's secret backend, so the - * keyspace is only correlatable by something that already holds the secret. - */ - private record KeyDigest(javax.crypto.Mac prototype) { - - static KeyDigest of( - SecretSource secretSource, String reference, String capability, RedisSdkSettings sdk) { - String name = secretName(reference, capability); - String material = - secretSource - .resolve(name) - .orElseThrow( - () -> - new IllegalStateException( - "the Redis " - + capability - + " key-digest secret '" - + name - + "' resolved to nothing. Without it the keyspace would carry" - + " identifiers in a form anybody who can read it could reverse.")); - try { - javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256"); - mac.init( - new javax.crypto.spec.SecretKeySpec( - // Namespace-bound: the same identifier in staging and production must not digest - // to the same key, or a keyspace dump from the softer environment would map - // one-for-one onto the harder one. - (material - + '|' - + sdk.getNamespace().getEnvironment() - + ':' - + sdk.getNamespace().getService() - + ':' - + sdk.getNamespace().getDomain()) - .getBytes(StandardCharsets.UTF_8), - "HmacSHA256")); - return new KeyDigest(mac); - } catch (java.security.GeneralSecurityException failure) { - throw new IllegalStateException("HmacSHA256 must be available", failure); - } - } - - private static String secretName(String reference, String capability) { - if (reference == null || reference.isBlank()) { - throw new IllegalStateException( - "the Redis " + capability + " capability needs a key-digest secret reference"); - } - int separator = reference.lastIndexOf('/'); - if (!reference.startsWith("secret://") || separator < 0) { - throw new IllegalStateException( - "the Redis " - + capability - + " key-digest reference '" - + reference - + "' must be secret:///"); - } - return reference.substring(separator + 1); - } - - String of(String key) { - try { - javax.crypto.Mac mac = (javax.crypto.Mac) prototype.clone(); - return "hv1:" - + java.util.HexFormat.of().formatHex(mac.doFinal(key.getBytes(StandardCharsets.UTF_8))); - } catch (CloneNotSupportedException failure) { - throw new IllegalStateException("a Mac must be cloneable", failure); - } - } - } -} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java index b8393da0..ebcde5fe 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/HikariPoolConstraintValidator.java @@ -1,9 +1,9 @@ package dev.caskeleton.bootstrap.runtime; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.bootstrap.runtime.startup.StartupPhase; +import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext; import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.boot.convert.DurationStyle; import org.springframework.core.env.Environment; @@ -35,7 +35,8 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton @Override public void afterSingletonsInstantiated() { - List violations = new ArrayList<>(); + StartupValidationContext violations = + StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION); Long connectionTimeout = parseMillis(CONNECTION_TIMEOUT_KEY, environment.getProperty(CONNECTION_TIMEOUT_KEY)); @@ -48,7 +49,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton parseMillis(LEAK_DETECTION_KEY, environment.getProperty(LEAK_DETECTION_KEY)); if (connectionTimeout != null && connectionTimeout < 250L) { - violations.add( + violations.reject( "D2/HIKARI-CFG-C1: " + CONNECTION_TIMEOUT_ENV_KEY + " (" @@ -60,7 +61,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton if (validationTimeout != null && connectionTimeout != null && validationTimeout >= connectionTimeout) { - violations.add( + violations.reject( "D7/HIKARI-CFG-C6: validation-timeout (" + VALIDATION_TIMEOUT_KEY + ", " @@ -76,7 +77,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton } if (keepaliveTime != null && maxLifetime != null && keepaliveTime >= maxLifetime) { - violations.add( + violations.reject( "D4/HIKARI-CFG-C4: keepalive-time (" + KEEPALIVE_TIME_KEY + ", " @@ -92,7 +93,7 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton } if (leakDetection != null && leakDetection != 0L && leakDetection < 2000L) { - violations.add( + violations.reject( "D5/HIKARI-CFG-C5: leak-detection-threshold (" + LEAK_DETECTION_KEY + ", " @@ -102,12 +103,8 @@ public class HikariPoolConstraintValidator implements SmartInitializingSingleton + leakDetection); } - if (!violations.isEmpty()) { - throw StartupFailures.envValidation( - "invalid HikariCP configuration (feature-database-connection-pool-contract" - + " D2/D4/D5/D7): " - + violations); - } + violations.failIfViolated( + "invalid HikariCP configuration (feature-database-connection-pool-contract D2/D4/D5/D7)"); } /** diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java index 6543460b..44310479 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/JpaSchemaSafetyValidator.java @@ -1,6 +1,8 @@ package dev.caskeleton.bootstrap.runtime; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; +import java.util.List; import java.util.Locale; import java.util.Set; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -15,7 +17,6 @@ public class JpaSchemaSafetyValidator implements SmartInitializingSingleton { static final String DDL_AUTO_KEY = "spring.jpa.hibernate.ddl-auto"; static final String DDL_AUTO_ENV_KEY = "APP_DATASOURCE_DDL_AUTO"; - private static final String PROD_PROFILE = "prod"; private static final Set PROD_ALLOWED_MODES = Set.of("none", "validate"); private final Environment environment; @@ -49,11 +50,6 @@ public class JpaSchemaSafetyValidator implements SmartInitializingSingleton { } private boolean isProdActive() { - for (String profile : environment.getActiveProfiles()) { - if (PROD_PROFILE.equalsIgnoreCase(profile)) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java index 84b9c3ca..42ac0d6c 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PersistenceVendorProdSafetyValidator.java @@ -1,6 +1,8 @@ package dev.caskeleton.bootstrap.runtime; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; +import java.util.List; import java.util.Locale; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.core.env.Environment; @@ -35,7 +37,6 @@ public final class PersistenceVendorProdSafetyValidator implements SmartInitiali static final String JDBC_URL_KEY = "spring.datasource.url"; static final String JDBC_URL_ENV_KEY = "APP_DATASOURCE_URL"; - private static final String PROD_PROFILE = "prod"; private static final String H2_VENDOR = "h2"; private static final String H2_URL_PREFIX = "jdbc:h2:"; @@ -79,11 +80,6 @@ public final class PersistenceVendorProdSafetyValidator implements SmartInitiali } private boolean isProdActive() { - for (String profile : environment.getActiveProfiles()) { - if (PROD_PROFILE.equalsIgnoreCase(profile)) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java index 1d5463ce..349ec989 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/PostgreSqlTransportSecurityValidator.java @@ -1,6 +1,7 @@ package dev.caskeleton.bootstrap.runtime; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -22,7 +23,6 @@ public final class PostgreSqlTransportSecurityValidator implements SmartInitiali static final String HIKARI_SSLMODE_KEY = "spring.datasource.hikari.data-source-properties.sslmode"; - private static final String PROD_PROFILE = "prod"; private static final String POSTGRESQL_PREFIX = "jdbc:postgresql:"; private static final String VERIFY_FULL = "verify-full"; private static final Pattern URL_SSLMODE = Pattern.compile("(?i)(?:[?&])sslmode=([^&]*)"); @@ -84,11 +84,6 @@ public final class PostgreSqlTransportSecurityValidator implements SmartInitiali } private boolean isProdActive() { - for (String profile : environment.getActiveProfiles()) { - if (PROD_PROFILE.equalsIgnoreCase(profile)) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java new file mode 100644 index 00000000..08f81472 --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/ReadinessGroup.java @@ -0,0 +1,66 @@ +package dev.caskeleton.bootstrap.runtime; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.MapPropertySource; + +/** + * The readiness health group, and the one way a capability adds itself to it. + * + *

Two capability post-processors — Redis and the datasource — each contributed a member by + * reading {@code management.endpoint.health.group.readiness.include}, splitting it on commas, + * trimming, adding their own contributor, and pushing a new property source to the front. The code + * was the same in both, and the property key itself was declared in the Redis one and reached from + * the database one, so a package move turned a shared contract into a compile error and revealed + * that Redis had never owned the key at all — it had simply declared it first. + * + *

The key belongs to the readiness group, so it lives here, and so does the merge. What stays + * with each capability is the only part that is actually theirs: whether they are active, and which + * contributor names them. + */ +public final class ReadinessGroup { + + /** The Spring property naming the health contributors the readiness probe aggregates. */ + public static final String INCLUDE_PROPERTY = + "management.endpoint.health.group.readiness.include"; + + private ReadinessGroup() {} + + /** + * Adds a contributor to the readiness group unless it is already named. + * + *

Returning without contributing when the member is already present is deliberate and not + * merely an optimisation: pushing a source that repeats what the operator wrote would be harmless + * at runtime and would hide, in {@code /actuator/configprops}, that they had asked for it + * themselves. + * + *

Contributions chain rather than race because the callers declare distinct orders in {@link + * dev.caskeleton.bootstrap.runtime.startup.StartupOrder}; each one reads what the previous + * contributed. + * + * @param environment the environment to contribute to + * @param propertySourceName a name unique to the contributing capability, so the source is + * identifiable in {@code /actuator/env} + * @param member the health contributor to include + */ + public static void include( + ConfigurableEnvironment environment, String propertySourceName, String member) { + String configured = environment.getProperty(INCLUDE_PROPERTY, ""); + Set members = new LinkedHashSet<>(); + Arrays.stream(configured.split(",")) + .map(String::trim) + .filter(existing -> !existing.isEmpty()) + .forEach(members::add); + if (!members.add(member)) { + return; + } + environment + .getPropertySources() + .addFirst( + new MapPropertySource( + propertySourceName, Map.of(INCLUDE_PROPERTY, String.join(",", members)))); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java index 05fd7518..e0ac185e 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeNumericBoundsValidator.java @@ -1,7 +1,7 @@ package dev.caskeleton.bootstrap.runtime; -import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; -import java.util.ArrayList; +import dev.caskeleton.bootstrap.runtime.startup.StartupPhase; +import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext; import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.core.env.Environment; @@ -35,14 +35,15 @@ public class RuntimeNumericBoundsValidator implements SmartInitializingSingleton @Override public void afterSingletonsInstantiated() { - List violations = new ArrayList<>(); + StartupValidationContext violations = + StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION); for (Bound bound : BOUNDS) { Integer value = environment.getProperty(bound.springKey(), Integer.class); if (value == null) { continue; // absent → skip (framework default owns it) } if (value < bound.minInclusive()) { - violations.add( + violations.reject( bound.envKey() + " (" + bound.springKey() @@ -52,11 +53,8 @@ public class RuntimeNumericBoundsValidator implements SmartInitializingSingleton + value); } } - if (!violations.isEmpty()) { - throw StartupFailures.envValidation( - "invalid runtime numeric configuration (feature-env-driven-runtime-configuration " - + "D10 — no lenient default): " - + violations); - } + violations.failIfViolated( + "invalid runtime numeric configuration (feature-env-driven-runtime-configuration " + + "D10 — no lenient default)"); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java new file mode 100644 index 00000000..b02de0ea --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RuntimeWarnings.java @@ -0,0 +1,85 @@ +package dev.caskeleton.bootstrap.runtime; + +import static net.logstash.logback.argument.StructuredArguments.kv; +import static net.logstash.logback.argument.StructuredArguments.v; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Single source for the two warnings this composition root actually emits. + * + *

{@code StartupFailures} already did this for the failures — one place, fixed structured + * fields, an operator who can filter on {@code error.code}. Nothing did it for the warnings, so + * seventeen {@code log.warn} calls each invented their own sentence and none of them carried a + * field (BOOT-009). Grepping "how often does a meter fail to record?" across a fleet meant matching + * prose. + * + *

Both methods below are frames, not wrappers: the fixed part is the field set, and the varying + * part is passed in. That is the distinction the review asked for — keep what must differ, fix what + * must not. + * + *

Why these two and no more

+ * + *

A warning is not a failure. Everything here describes a runtime that is continuing: a meter + * that could not be recorded, a setting that was replaced by its default. Anything that should stop + * the process belongs in {@code StartupFailures} instead, and inventing a third frame before a + * third shape exists would be the speculative generality this repository's own review argued + * against. + */ +public final class RuntimeWarnings { + + private static final Logger log = LoggerFactory.getLogger("dev.caskeleton.bootstrap.runtime"); + + private RuntimeWarnings() {} + + /** + * A meter could not be registered, recorded or refreshed. + * + *

Observability failing is never a reason to fail the operation being observed, so every + * caller of this swallows the cause and continues. That is exactly why the warning has to be + * findable: it is the only trace that a metric an operator is watching has silently stopped + * moving. + * + * @param capability the capability whose meter failed — {@code outbox}, {@code distributed-lock}, + * {@code tracing}, {@code background-job} + * @param meter the meter name or the operation that failed + * @param cause the failure + */ + public static void meterUnavailable(String capability, String meter, Throwable cause) { + // v(), not kv(): both attach the field, but kv() also renders "name=value" into the message + // text, which reads as noise in a plain-text console and duplicates what the JSON encoder + // already emits. The rendered sentence stays a sentence; the fields are still there. + log.warn( + "{} metrics: {} could not be recorded", + v("capability", capability), + v("meter", meter), + kv("warning.kind", "meter-unavailable"), + cause); + } + + /** + * A configured value was rejected and its default applied instead. + * + *

Deliberately a warning rather than a startup failure: these settings shape logging, and + * refusing to start because a log rotation count was zero would make the observability + * configuration able to take the application down. The trade is that the deployment runs with a + * value nobody asked for, so the message names the key, what was rejected and what is in force — + * an operator reading only the applied value would never know their configuration was ignored. + * + * @param environmentKey the environment variable an operator would edit + * @param rejected the configured value that was refused; may be {@code null} or blank + * @param applied the value actually in force + * @param reason why the configured value was refused + */ + public static void settingFallback( + String environmentKey, Object rejected, Object applied, String reason) { + log.warn( + "{} was rejected and its default applied: {} (rejected '{}', in force '{}')", + v("env.key", environmentKey), + reason, + v("setting.rejected", String.valueOf(rejected)), + v("setting.applied", String.valueOf(applied)), + kv("warning.kind", "setting-fallback")); + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java index 2a037024..12f49578 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceConfig.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.runtime; +import dev.caskeleton.bootstrap.autoconfigure.redis.RedisActivationValidator; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java index f955e4e6..640ac834 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/SecretSourceValidator.java @@ -1,6 +1,7 @@ package dev.caskeleton.bootstrap.runtime; import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -26,8 +27,6 @@ public class SecretSourceValidator implements SmartInitializingSingleton { */ public static final String REDIS_ENABLED_PROPERTY = "app.redis.enabled"; - private static final String PROD_PROFILE = "prod"; - /** * Secret keys that must be injected under the {@code prod} profile. {@code * SecretsClassificationRegistryTest} asserts this list matches the registry 1:1. See README for @@ -171,12 +170,6 @@ public class SecretSourceValidator implements SmartInitializingSingleton { } private boolean isProdActive() { - // Case-insensitive: a typo such as SPRING_PROFILES_ACTIVE=PROD must still match. - for (String profile : environment.getActiveProfiles()) { - if (PROD_PROFILE.equalsIgnoreCase(profile)) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java index c89a5531..49ae8023 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/StartupSafetyValidator.java @@ -1,8 +1,9 @@ package dev.caskeleton.bootstrap.runtime; -import dev.caskeleton.bootstrap.runtime.startup.StartupFailures; +import dev.caskeleton.bootstrap.runtime.startup.StartupPhase; +import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext; import dev.caskeleton.bootstrap.settings.RuntimeSafetySettings; -import java.util.ArrayList; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.util.List; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.SmartInitializingSingleton; @@ -29,8 +30,6 @@ public class StartupSafetyValidator implements SmartInitializingSingleton { "migrationStartupRunner" // platform migration startup job ); - private static final String PROD_PROFILE = "prod"; - private final Environment environment; private final RuntimeSafetySettings settings; private final ListableBeanFactory beanFactory; @@ -53,20 +52,18 @@ public class StartupSafetyValidator implements SmartInitializingSingleton { if (!isProdActive()) { return; } - List unsafe = new ArrayList<>(); + StartupValidationContext unsafe = StartupValidationContext.forPhase(StartupPhase.PROFILE_CHECK); if (settings.errorDetailExposureEnabled()) { - unsafe.add("APP_ERROR_DETAIL_EXPOSURE_ENABLED"); + unsafe.reject("APP_ERROR_DETAIL_EXPOSURE_ENABLED"); } if (settings.logBodyCaptureEnabled()) { - unsafe.add("APP_LOG_BODY_CAPTURE_ENABLED"); - } - if (!unsafe.isEmpty()) { - // prod-unsafe toggle left on under prod → PROFILE_MISMATCH (exit 71). - throw StartupFailures.profileMismatch( - "prod profile forbids these toggles being enabled: " - + unsafe - + " — set them to false for the prod profile."); + unsafe.reject("APP_LOG_BODY_CAPTURE_ENABLED"); } + // The phase the context was opened for is what selects PROFILE_MISMATCH (exit 71); the + // classification is no longer restated at the throw site. + unsafe.failIfViolated( + "prod profile forbids these toggles being enabled", + "set them to false for the prod profile."); } /** When multi-instance is on, every instance-coordination capability bean must exist. */ @@ -74,30 +71,21 @@ public class StartupSafetyValidator implements SmartInitializingSingleton { if (!settings.multiInstanceEnabled()) { return; } - List missing = new ArrayList<>(); + StartupValidationContext missing = + StartupValidationContext.forPhase(StartupPhase.ADAPTER_ENABLEMENT); for (String beanName : REQUIRED_MULTI_INSTANCE_BEANS) { if (!beanFactory.containsBean(beanName)) { - missing.add(beanName); + missing.reject(beanName); } } - if (!missing.isEmpty()) { - // required coordination bean absent under multi-instance → REQUIRED_ADAPTER_DISABLED (exit - // 72). - throw StartupFailures.requiredAdapterDisabled( - "APP_MULTI_INSTANCE_ENABLED=true requires the instance-coordination beans " - + REQUIRED_MULTI_INSTANCE_BEANS - + ", but these are missing: " - + missing); - } + // ADAPTER_ENABLEMENT (exit 72) follows from the context's phase. + missing.failIfViolated( + "APP_MULTI_INSTANCE_ENABLED=true requires the instance-coordination beans " + + REQUIRED_MULTI_INSTANCE_BEANS + + ", but these are missing"); } private boolean isProdActive() { - // Case-insensitive: a typo such as SPRING_PROFILES_ACTIVE=PROD must still match. - for (String profile : environment.getActiveProfiles()) { - if (PROD_PROFILE.equalsIgnoreCase(profile)) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java index eea2bc1d..c46959a2 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/FlywayProdSafetyValidator.java @@ -1,6 +1,6 @@ package dev.caskeleton.bootstrap.runtime.startup; -import java.util.ArrayList; +import dev.caskeleton.shared.runtime.RuntimeEnvironment; import java.util.List; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.core.env.Environment; @@ -15,8 +15,6 @@ public class FlywayProdSafetyValidator implements SmartInitializingSingleton { static final String OUT_OF_ORDER_KEY = "spring.flyway.out-of-order"; static final String CLEAN_DISABLED_KEY = "spring.flyway.clean-disabled"; - private static final String PROD_PROFILE = "prod"; - private final Environment environment; public FlywayProdSafetyValidator(Environment environment) { @@ -28,24 +26,22 @@ public class FlywayProdSafetyValidator implements SmartInitializingSingleton { if (!isProdActive()) { return; } - List violations = new ArrayList<>(); + StartupValidationContext violations = + StartupValidationContext.forPhase(StartupPhase.PROFILE_CHECK); if (isTrue(BASELINE_ON_MIGRATE_KEY)) { - violations.add(BASELINE_ON_MIGRATE_KEY + "=true (removes the missing-migration safety net)"); + violations.reject( + BASELINE_ON_MIGRATE_KEY + "=true (removes the missing-migration safety net)"); } if (isTrue(OUT_OF_ORDER_KEY)) { - violations.add(OUT_OF_ORDER_KEY + "=true (breaks migration ordering consistency)"); + violations.reject(OUT_OF_ORDER_KEY + "=true (breaks migration ordering consistency)"); } if (isFalse(CLEAN_DISABLED_KEY)) { - violations.add(CLEAN_DISABLED_KEY + "=false (re-arms destructive Flyway clean)"); - } - if (!violations.isEmpty()) { - throw StartupFailures.profileMismatch( - "prod profile forbids these Flyway options (feature-migration-startup-contract" - + " D2/D4): " - + violations - + " — partial-schema recovery goes through runbook://migration/manual-recovery," - + " never an in-prod Flyway repair."); + violations.reject(CLEAN_DISABLED_KEY + "=false (re-arms destructive Flyway clean)"); } + violations.failIfViolated( + "prod profile forbids these Flyway options (feature-migration-startup-contract D2/D4)", + "partial-schema recovery goes through runbook://migration/manual-recovery, never an" + + " in-prod Flyway repair."); } private boolean isTrue(String key) { @@ -58,12 +54,6 @@ public class FlywayProdSafetyValidator implements SmartInitializingSingleton { } private boolean isProdActive() { - // Case-insensitive so a SPRING_PROFILES_ACTIVE=PROD typo still triggers the guard. - for (String profile : environment.getActiveProfiles()) { - if (PROD_PROFILE.equalsIgnoreCase(profile)) { - return true; - } - } - return false; + return RuntimeEnvironment.isProductionActive(List.of(environment.getActiveProfiles())); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java index be460938..bb48f8d8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/RequiredEnvironmentValidator.java @@ -1,7 +1,5 @@ package dev.caskeleton.bootstrap.runtime.startup; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.core.env.Environment; @@ -28,7 +26,8 @@ public class RequiredEnvironmentValidator implements SmartInitializingSingleton @Override public void afterSingletonsInstantiated() { - List missing = new ArrayList<>(); + StartupValidationContext missing = + StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION); // Sorted for a stable, deterministic message regardless of map iteration order. REQUIRED_DATASOURCE_ENV.entrySet().stream() .sorted(Map.Entry.comparingByValue()) @@ -36,14 +35,11 @@ public class RequiredEnvironmentValidator implements SmartInitializingSingleton entry -> { String value = environment.getProperty(entry.getKey()); if (value == null || value.isBlank()) { - missing.add(entry.getValue() + " (" + entry.getKey() + ")"); + missing.reject(entry.getValue() + " (" + entry.getKey() + ")"); } }); - if (!missing.isEmpty()) { - throw StartupFailures.envValidation( - "required datasource environment is missing or blank — a Flyway migration" - + " cannot run without it: " - + missing); - } + missing.failIfViolated( + "required datasource environment is missing or blank — a Flyway migration" + + " cannot run without it"); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java index 655c9e1d..d9869e89 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupFailures.java @@ -18,8 +18,19 @@ public final class StartupFailures { private StartupFailures() {} public static StartupValidationException envValidation(String detail) { - emit(StartupErrorCode.STARTUP_VALIDATION_FAILED, detail, null); - return new StartupValidationException(detail); + return envValidation(detail, null); + } + + /** + * Raises an environment/configuration validation failure that a lower-level exception proved. + * + * @param detail the operator-facing detail + * @param cause the failure that proved the configuration wrong; may be {@code null} + * @return the exception to throw + */ + public static StartupValidationException envValidation(String detail, Throwable cause) { + emit(StartupErrorCode.STARTUP_VALIDATION_FAILED, detail, cause); + return new StartupValidationException(detail, cause); } public static ProfileMismatchException profileMismatch(String detail) { diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java new file mode 100644 index 00000000..19afd86a --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupOrder.java @@ -0,0 +1,66 @@ +package dev.caskeleton.bootstrap.runtime.startup; + +import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor; +import org.springframework.core.Ordered; + +/** + * The order every {@code EnvironmentPostProcessor} in this composition root runs in, in one place. + * + *

Six post-processors are registered in {@code META-INF/spring.factories} and their relative + * order is part of how they work — a capability violation reported against a key the master switch + * was about to rewrite sends an operator somewhere useless. Three of them nonetheless returned the + * same value, {@code Ordered.LOWEST_PRECEDENCE}, and a fourth declared no order at all (BOOT-005). + * The sequence held only because Spring sorts stably and {@code spring.factories} happened to list + * them in the intended order: reordering four lines in a text file would have silently changed + * behaviour, and nothing would have failed. + * + *

{@link dev.caskeleton.bootstrap.activation.CapabilityDependencyEnvironmentValidator} even + * documented the constraint in its {@code getOrder()} Javadoc — "after {@code + * MasterSwitchEnvironmentPostProcessor} has rejected legacy switch spellings" — while returning a + * value that could not express it. A comment stating an ordering its return value does not enforce + * is the shape this class exists to remove. + * + *

Two bands, and why

+ * + *

The readiness post-processors contribute property sources and run immediately after config + * data, so they are expressed relative to {@link ConfigDataEnvironmentPostProcessor#ORDER}. The + * validators must observe the environment the application will actually run with — every + * contributed source included — so they stay at the {@code LOWEST_PRECEDENCE} end and are separated + * from each other by single steps. Keeping the last of them at exactly {@code LOWEST_PRECEDENCE} + * preserves this repository's existing position relative to any third-party post-processor at that + * value, so this change makes an undefined order deterministic without moving the whole band. + */ +public final class StartupOrder { + + private StartupOrder() {} + + /** Redis readiness group contribution — a property source, immediately after config data. */ + public static final int READINESS_REDIS = ConfigDataEnvironmentPostProcessor.ORDER + 1; + + /** Database readiness group contribution — a property source, immediately after config data. */ + public static final int READINESS_DATABASE = ConfigDataEnvironmentPostProcessor.ORDER + 2; + + /** + * Master-switch normalisation. First of the validators, because it rewrites and rejects legacy + * switch spellings; every rule after it reads the keys an operator will actually be told about. + */ + public static final int MASTER_SWITCH_NORMALISATION = Ordered.LOWEST_PRECEDENCE - 3; + + /** + * Runtime environment profile rule. After master-switch normalisation and before capability + * checks: which environment this is decides which capability combinations are even legal. + */ + public static final int RUNTIME_ENVIRONMENT_PROFILE = Ordered.LOWEST_PRECEDENCE - 2; + + /** + * Capability dependency graph. Last of the fail-closed rules, so its message names post-migration + * keys under a runtime environment that has already been established as valid. + */ + public static final int CAPABILITY_DEPENDENCY = Ordered.LOWEST_PRECEDENCE - 1; + + /** + * Tracing sample-rate defaulting. Runs after every rule that can refuse the boot outright — there + * is no value in defaulting a sample rate for a process that is about to exit. + */ + public static final int TRACING_SAMPLING = Ordered.LOWEST_PRECEDENCE; +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java new file mode 100644 index 00000000..db917fab --- /dev/null +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationContext.java @@ -0,0 +1,173 @@ +package dev.caskeleton.bootstrap.runtime.startup; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Collects every violation a startup guard finds, then raises them as one typed failure. + * + *

Fifteen guards in this leaf wrote the same shape by hand: a {@code new ArrayList<>()}, a + * series of {@code if (...) list.add(...)}, and a closing {@code if (!list.isEmpty()) throw ...} + * (BOOT-004). They did not even agree on what to call it — {@code violations}, {@code missing}, + * {@code offenders}, {@code contradictions}, {@code unsafe}, {@code reasons}, {@code modes} — so + * the fact that they were one pattern was invisible in the code, and each copy independently chose + * how to join the messages and which exception to throw. + * + *

Accumulate, do not fail fast

+ * + *

Collecting every violation before failing is the point, and it is worth stating because the + * opposite is the more common instinct. An operator fixing a misconfigured deployment works in + * cycles of edit, restart, read the error. Reporting one problem at a time turns a single bad + * configuration into as many restarts as it has mistakes; reporting all of them costs nothing and + * ends the loop in one pass. This is also why Bean Validation's {@code @GroupSequence} was the + * wrong instrument here — group sequencing short-circuits, which is precisely the behaviour that + * must not happen inside one phase. + * + *

The phase chooses the exception

+ * + *

A context is opened for a {@link StartupPhase}, and the phase decides which {@link + * StartupFailures} factory raises the result — so the exit code, the {@code error.code} field and + * the {@code startup.phase} log field follow from one declaration at the top of the guard instead + * of being restated at the throw site. Before this, {@code StartupPhase} existed but was only ever + * written into a log message; nothing read it, and nothing derived behaviour from it (BOOT-005). + */ +public final class StartupValidationContext { + + private final StartupPhase phase; + private final List violations = new ArrayList<>(); + + private StartupValidationContext(StartupPhase phase) { + this.phase = Objects.requireNonNull(phase, "phase"); + } + + /** + * Opens a context for the given phase. + * + *

{@link StartupPhase#MIGRATION} is refused here rather than at the failure site. A migration + * failure always carries the Flyway exception that caused it and is raised through {@link + * StartupFailures#migrationFailed}; a context cannot invent that cause. Rejecting the phase at + * construction puts the error where the mistake is — the guard that opened the wrong context — + * instead of surfacing it only on the day something actually goes wrong. + * + * @param phase the startup phase whose rules this context collects + * @return a new, empty context + * @throws IllegalArgumentException when the phase is {@code MIGRATION} + */ + public static StartupValidationContext forPhase(StartupPhase phase) { + if (phase == StartupPhase.MIGRATION) { + throw new IllegalArgumentException( + "StartupPhase.MIGRATION cannot be accumulated: a migration failure carries the exception " + + "that caused it and is raised through StartupFailures.migrationFailed(detail, cause)"); + } + return new StartupValidationContext(phase); + } + + /** The phase this context collects for. */ + public StartupPhase phase() { + return phase; + } + + /** + * Records a violation unless the condition holds. + * + * @param satisfied the rule's outcome — {@code true} means nothing to record + * @param violation the operator-facing description of what is wrong, evaluated eagerly + * @return this context, so rules can be chained + */ + public StartupValidationContext require(boolean satisfied, String violation) { + if (!satisfied) { + reject(violation); + } + return this; + } + + /** + * Records a violation unconditionally. + * + * @param violation the operator-facing description of what is wrong + * @return this context, so rules can be chained + */ + public StartupValidationContext reject(String violation) { + violations.add(Objects.requireNonNull(violation, "violation")); + return this; + } + + /** + * Records every given violation. + * + * @param found violations produced elsewhere — a shared rule evaluated by another class + * @return this context, so rules can be chained + */ + public StartupValidationContext rejectAll(List found) { + for (String violation : found) { + reject(violation); + } + return this; + } + + /** + * Sorts the collected violations so the message is the same on every host. + * + *

For a guard that walks a {@code Map}, insertion order is iteration order and iteration order + * is not a contract — the same misconfiguration would otherwise produce a different message from + * one JVM to the next, which is exactly the kind of difference that makes an operator wonder + * whether they are looking at two problems. + * + * @return this context, so rules can be chained + */ + public StartupValidationContext sorted() { + violations.sort(String::compareTo); + return this; + } + + /** Reports whether anything was recorded. */ + public boolean hasViolations() { + return !violations.isEmpty(); + } + + /** Everything recorded so far, in the order it was found. */ + public List violations() { + return List.copyOf(violations); + } + + /** + * Raises the collected violations as the failure this phase corresponds to, or returns quietly. + * + *

The summary is what an operator reads first, so it should name the thing being validated + * rather than repeat that validation failed; the violations follow it, semicolon-separated. + * + * @param summary a short lead-in naming what was being validated + */ + public void failIfViolated(String summary) { + failIfViolated(summary, null); + } + + /** + * Raises the collected violations, followed by what the operator should do about them. + * + *

The remedy is a separate parameter rather than part of the summary because it belongs after + * the list: "these options are forbidden: a; b — recovery goes through runbook://…" reads as + * instructions, while the same sentence before the list reads as a description of the list. + * + * @param summary a short lead-in naming what was being validated + * @param remedy what to do instead; may be {@code null} + */ + public void failIfViolated(String summary, String remedy) { + if (violations.isEmpty()) { + return; + } + String detail = + summary + ": " + String.join("; ", violations) + (remedy == null ? "" : " — " + remedy); + throw switch (phase) { + case ENV_VALIDATION -> StartupFailures.envValidation(detail); + case PROFILE_CHECK -> StartupFailures.profileMismatch(detail); + case ADAPTER_ENABLEMENT -> StartupFailures.requiredAdapterDisabled(detail); + // Unreachable: forPhase refuses MIGRATION. The arm exists so the switch stays exhaustive + // without a `default`, which is what makes the compiler object if a fifth phase is added + // without deciding how it fails. + case MIGRATION -> + throw new AssertionError("forPhase rejects MIGRATION; collected: " + detail); + }; + } +} diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java index cfbfcee4..91bad398 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/startup/StartupValidationException.java @@ -9,6 +9,21 @@ public class StartupValidationException extends StartupFailureException { private static final long serialVersionUID = 1L; public StartupValidationException(String message) { - super(StartupErrorCode.STARTUP_VALIDATION_FAILED, message, null); + this(message, null); + } + + /** + * Carries the underlying failure alongside the message. + * + *

Some configuration is only provably wrong by trying it — a datasource URL is valid text + * until a connection attempt refuses it. Those checks hold a {@code SQLException} whose message + * names the host, the port and the refusal, and dropping it would leave an operator with "could + * not open a connection" and nothing to act on. + * + * @param message the operator-facing detail + * @param cause the failure that proved the configuration wrong; may be {@code null} + */ + public StartupValidationException(String message, Throwable cause) { + super(StartupErrorCode.STARTUP_VALIDATION_FAILED, message, cause); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java index 784c939c..c3c6bd60 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeCompositionConfig.java @@ -1,7 +1,7 @@ package dev.caskeleton.bootstrap.security; -import java.util.ArrayList; -import java.util.List; +import dev.caskeleton.bootstrap.runtime.startup.StartupPhase; +import dev.caskeleton.bootstrap.runtime.startup.StartupValidationContext; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.SmartInitializingSingleton; import org.springframework.boot.context.properties.EnableConfigurationProperties; @@ -21,28 +21,14 @@ public class AuthenticationModeCompositionConfig { static void validate( AuthenticationModeSettings.AuthenticationMode mode, ListableBeanFactory beans) { - List contradictions = new ArrayList<>(); - boolean jwtDecoder = beans.containsBean("jwtDecoder"); - boolean sessionRepository = beans.containsBean("redisVersionedSessionRepository"); - boolean sessionFilter = beans.containsBean("springSessionRepositoryFilter"); - if (mode == AuthenticationModeSettings.AuthenticationMode.JWT) { - if (!jwtDecoder) { - contradictions.add("jwtDecoder is absent"); - } - if (sessionRepository || sessionFilter) { - contradictions.add("Redis Session repository/filter is active"); - } - } else { - if (jwtDecoder) { - contradictions.add("jwtDecoder is active"); - } - if (!sessionRepository || !sessionFilter) { - contradictions.add("Redis Session repository/filter is incomplete"); - } + StartupValidationContext contradictions = + StartupValidationContext.forPhase(StartupPhase.ENV_VALIDATION); + for (String required : mode.requiredBeans()) { + contradictions.require(beans.containsBean(required), required + " is absent"); } - if (!contradictions.isEmpty()) { - throw new IllegalStateException( - "Authentication mode composition is not exclusive for " + mode + ": " + contradictions); + for (String forbidden : mode.forbiddenBeans()) { + contradictions.require(!beans.containsBean(forbidden), forbidden + " is active"); } + contradictions.failIfViolated("Authentication mode composition is not exclusive for " + mode); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java index 40a72d06..b0ca3548 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/security/AuthenticationModeSettings.java @@ -1,5 +1,6 @@ package dev.caskeleton.bootstrap.security; +import java.util.Set; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.bind.ConstructorBinding; @@ -12,8 +13,56 @@ public record AuthenticationModeSettings(AuthenticationMode authMode) { this.authMode = authMode == null ? AuthenticationMode.JWT : authMode; } + /** + * The exclusive authentication modes, each carrying the beans it requires and forbids. + * + *

The composition guard used to decide this with an {@code if (mode == JWT) … else …}, so a + * third mode would have taken the {@code else} branch and been validated as though it were Redis + * Session — a wrong answer rather than a refusal (BOOT-010). Declaring the two bean sets on the + * constant means a third mode cannot be added without naming them: the abstract methods below + * make the compiler ask. + * + *

Constant bodies rather than constructor arguments, and that is not a style preference. Error + * Prone's {@code ImmutableEnumChecker} rejects a {@code Set} field in an enum even when it holds + * {@code Set.copyOf(...)}, because it reasons about the declared type. A method has no field to + * object to, and it keeps each constant's answer next to the constant. + * + *

Bean names rather than types, because that is what the guard can ask a {@code + * ListableBeanFactory} without instantiating anything: the point is to check the shape of the + * graph, not to build it. + */ public enum AuthenticationMode { - JWT, - REDIS_SESSION + + /** Stateless bearer tokens: a decoder, and no session infrastructure at all. */ + JWT { + @Override + public Set requiredBeans() { + return Set.of("jwtDecoder"); + } + + @Override + public Set forbiddenBeans() { + return Set.of("redisVersionedSessionRepository", "springSessionRepositoryFilter"); + } + }, + + /** Server-side sessions in Redis: the repository and its filter, and no decoder. */ + REDIS_SESSION { + @Override + public Set requiredBeans() { + return Set.of("redisVersionedSessionRepository", "springSessionRepositoryFilter"); + } + + @Override + public Set forbiddenBeans() { + return Set.of("jwtDecoder"); + } + }; + + /** Beans that must exist for this mode to be able to authenticate a request. */ + public abstract Set requiredBeans(); + + /** Beans that must not exist, because they would authenticate a request the other way. */ + public abstract Set forbiddenBeans(); } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java index f334f46e..3af317a2 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/LoggingSettings.java @@ -1,8 +1,7 @@ package dev.caskeleton.bootstrap.settings; +import dev.caskeleton.bootstrap.runtime.RuntimeWarnings; import java.time.ZoneId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -15,17 +14,17 @@ public record LoggingSettings(File file, Async async, Json json) { public record File( boolean enabled, String path, String maxSize, int maxHistory, String totalSizeCap) { - private static final Logger log = LoggerFactory.getLogger(File.class); public File { if (enabled) { if (path == null || path.isBlank()) { - log.warn( - "APP_LOG_FILE_PATH is blank while APP_LOG_FILE_ENABLED=true; using 'logs/app.json'"); + RuntimeWarnings.settingFallback( + "APP_LOG_FILE_PATH", path, "logs/app.json", "it is blank while file logging is on"); path = "logs/app.json"; } if (maxHistory <= 0) { - log.warn("APP_LOG_FILE_MAX_HISTORY must be >= 1 (got {}); using 14", maxHistory); + RuntimeWarnings.settingFallback( + "APP_LOG_FILE_MAX_HISTORY", maxHistory, 14, "it must be >= 1"); maxHistory = 14; } // Size strings (maxSize, totalSizeCap) are parsed by logback; let it own that contract. @@ -34,18 +33,17 @@ public record LoggingSettings(File file, Async async, Json json) { } public record Async(boolean enabled, int queueSize, int discardingThreshold) { - private static final Logger log = LoggerFactory.getLogger(Async.class); public Async { if (enabled) { if (queueSize <= 0) { - log.warn("APP_LOG_ASYNC_QUEUE_SIZE must be >= 1 (got {}); using 512", queueSize); + RuntimeWarnings.settingFallback( + "APP_LOG_ASYNC_QUEUE_SIZE", queueSize, 512, "it must be >= 1"); queueSize = 512; } if (discardingThreshold < 0) { - log.warn( - "APP_LOG_ASYNC_DISCARDING_THRESHOLD must be >= 0 (got {}); using 20", - discardingThreshold); + RuntimeWarnings.settingFallback( + "APP_LOG_ASYNC_DISCARDING_THRESHOLD", discardingThreshold, 20, "it must be >= 0"); discardingThreshold = 20; } } @@ -54,32 +52,31 @@ public record LoggingSettings(File file, Async async, Json json) { public record Json( String timezone, String timestampPattern, boolean includeCallerData, int loggerNameLength) { - private static final Logger log = LoggerFactory.getLogger(Json.class); private static final String DEFAULT_TIMEZONE = "UTC"; private static final String DEFAULT_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"; public Json { if (timezone == null || timezone.isBlank()) { - log.warn("APP_LOG_JSON_TIMEZONE is blank; using '{}'", DEFAULT_TIMEZONE); + RuntimeWarnings.settingFallback( + "APP_LOG_JSON_TIMEZONE", timezone, DEFAULT_TIMEZONE, "it is blank"); timezone = DEFAULT_TIMEZONE; } else if (!"default".equalsIgnoreCase(timezone)) { try { ZoneId.of(timezone); } catch (Exception e) { - log.warn( - "APP_LOG_JSON_TIMEZONE '{}' is not a valid IANA zone; using '{}'", - timezone, - DEFAULT_TIMEZONE); + RuntimeWarnings.settingFallback( + "APP_LOG_JSON_TIMEZONE", timezone, DEFAULT_TIMEZONE, "it is not a valid IANA zone"); timezone = DEFAULT_TIMEZONE; } } if (timestampPattern == null || timestampPattern.isBlank()) { - log.warn("APP_LOG_JSON_TIMESTAMP_PATTERN is blank; using ISO 8601 default"); + RuntimeWarnings.settingFallback( + "APP_LOG_JSON_TIMESTAMP_PATTERN", timestampPattern, DEFAULT_PATTERN, "it is blank"); timestampPattern = DEFAULT_PATTERN; } if (loggerNameLength < 0) { - log.warn( - "APP_LOG_JSON_LOGGER_NAME_LENGTH must be >= 0 (got {}); using 0", loggerNameLength); + RuntimeWarnings.settingFallback( + "APP_LOG_JSON_LOGGER_NAME_LENGTH", loggerNameLength, 0, "it must be >= 0"); loggerNameLength = 0; } } diff --git a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java index d237d4c0..4e4de4d8 100644 --- a/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java +++ b/src/app-bootstrap/src/main/java/dev/caskeleton/bootstrap/settings/PrivacySettings.java @@ -1,8 +1,7 @@ package dev.caskeleton.bootstrap.settings; +import dev.caskeleton.bootstrap.runtime.RuntimeWarnings; import java.nio.charset.StandardCharsets; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -14,16 +13,16 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "ca-skeleton.privacy") public record PrivacySettings(String pseudonymizationSalt) { - private static final Logger log = LoggerFactory.getLogger(PrivacySettings.class); - /** Dev/local fallback salt — the {@code __LOCAL_DEV_} prefix is the prod-unsafe sentinel. */ static final String DEV_SENTINEL_SALT = "__LOCAL_DEV_pseudonymization_salt"; public PrivacySettings { if (pseudonymizationSalt == null || pseudonymizationSalt.isBlank()) { - log.warn( - "APP_PRIVACY_PSEUDONYMIZATION_SALT is blank; using a dev sentinel salt. " - + "Set a real secret-manager value before production."); + RuntimeWarnings.settingFallback( + "APP_PRIVACY_PSEUDONYMIZATION_SALT", + pseudonymizationSalt, + "a dev sentinel salt", + "it is blank; set a real secret-manager value before production"); pseudonymizationSalt = DEV_SENTINEL_SALT; } } diff --git a/src/app-bootstrap/src/main/resources/META-INF/spring.factories b/src/app-bootstrap/src/main/resources/META-INF/spring.factories index 4ddb4387..40f0c7bf 100644 --- a/src/app-bootstrap/src/main/resources/META-INF/spring.factories +++ b/src/app-bootstrap/src/main/resources/META-INF/spring.factories @@ -2,15 +2,15 @@ org.springframework.boot.EnvironmentPostProcessor=\ dev.caskeleton.bootstrap.activation.MasterSwitchEnvironmentPostProcessor,\ dev.caskeleton.bootstrap.activation.RuntimeEnvironmentProfileValidator,\ dev.caskeleton.bootstrap.activation.CapabilityDependencyEnvironmentValidator,\ -dev.caskeleton.bootstrap.tracing.TracingSamplingEnvironmentPostProcessor,\ -dev.caskeleton.bootstrap.runtime.RedisReadinessGroupPostProcessor,\ -dev.caskeleton.bootstrap.runtime.DatabaseReadinessGroupPostProcessor +dev.caskeleton.bootstrap.autoconfigure.tracing.TracingSamplingEnvironmentPostProcessor,\ +dev.caskeleton.bootstrap.autoconfigure.redis.RedisReadinessGroupPostProcessor,\ +dev.caskeleton.bootstrap.autoconfigure.jpa.DatabaseReadinessGroupPostProcessor org.springframework.boot.SpringBootExceptionReporter=\ dev.caskeleton.bootstrap.runtime.startup.StartupFailureExceptionReporter org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\ -dev.caskeleton.bootstrap.autoconfigure.persistencejpa.JpaOffAutoConfigurationImportFilter +dev.caskeleton.bootstrap.autoconfigure.jpa.JpaOffAutoConfigurationImportFilter # Corrects the structured log's profile field once the environment has resolved. An # ApplicationListener, not a bean: this event fires before there is a context to hold one. diff --git a/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports index 9b104ba1..643c527d 100644 --- a/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +++ b/src/app-bootstrap/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -1,6 +1,6 @@ dev.caskeleton.bootstrap.autoconfigure.fileserver.FileserverPlatformAutoConfiguration dev.caskeleton.bootstrap.autoconfigure.httpclient.HttpClientPlatformAutoConfiguration -dev.caskeleton.bootstrap.autoconfigure.persistencejpa.PersistenceJpaRootAutoConfiguration +dev.caskeleton.bootstrap.autoconfigure.jpa.PersistenceJpaRootAutoConfiguration dev.caskeleton.bootstrap.autoconfigure.messaging.DisabledMessagingSentinelAutoConfiguration -dev.caskeleton.bootstrap.notification.NotificationRootAutoConfiguration +dev.caskeleton.bootstrap.autoconfigure.notification.NotificationRootAutoConfiguration dev.caskeleton.bootstrap.activation.AdapterActivationAutoConfiguration diff --git a/src/app-bootstrap/src/main/resources/application.yml b/src/app-bootstrap/src/main/resources/application.yml index f9994cc6..37d38464 100644 --- a/src/app-bootstrap/src/main/resources/application.yml +++ b/src/app-bootstrap/src/main/resources/application.yml @@ -3,6 +3,13 @@ # input validation lives in the *Settings records under each module. # ============================================================================= +# 이 파일은 프레임워크 설정(spring.*)과 취합 목록만 가진다. +# +# 974줄 한 파일이던 것을 능력별로 나눴다(BOOT-008). spring.* 만 남은 이유는 하나다: +# spring.profiles.active 는 import 된 문서에 두면 프로파일이 결정되는 시점과 문서가 읽히는 시점이 +# 엇갈린다. 프레임워크 설정은 능력이 아니므로 능력별 파일로 나눌 대상도 아니다. +# +# 우선순위: 환경변수/CLI > application-{profile}.yml > application.yml > config/*.yml spring: application: # free-form string (also exposed as the JSON log "app" field) @@ -222,753 +229,20 @@ spring: # duration: 30s | 1m | 500ms timeout-per-shutdown-phase: ${APP_SERVER_SHUTDOWN_TIMEOUT:30s} -# --------------------------------------------------------------------------- -# Actuator / management — MERGED ownership at integration: -# - feature-management-actuator-security-contract owns server.port, the exposure -# allowlist/exclude, health show-details, shutdown-disabled, and info. -# - feature-runtime-health-lifecycle-contract owns the health probe SHAPE -# (probes.enabled + the liveness/readiness/startup groups + dependency taxonomy). -# --------------------------------------------------------------------------- -# --------------------------------------------------------------------------- -# OpenAPI. Stated rather than defaulted: SpringDoc logs a startup WARN telling every deployment to -# decide this, and a warning that appears on every start is one nobody reads. The template decides — -# on where it is useful, off under prod, which application-prod.yml pins so the choice cannot be -# widened by an environment variable in the one runtime where publishing the whole API shape is a -# reconnaissance surface. -# --------------------------------------------------------------------------- -springdoc: - api-docs: - enabled: ${APP_OPENAPI_DOCS_ENABLED:true} - swagger-ui: - enabled: ${APP_OPENAPI_UI_ENABLED:true} - -management: - # feature-management-actuator-security-contract D1: separate management port so actuator - # endpoints are not exposed on the same socket as the app API. Default: 9001. - server: - port: ${MANAGEMENT_SERVER_PORT:9001} - endpoints: - web: - # D2: production allowlist — only safe, scrape-friendly endpoints are exposed. - exposure: - # adapteractivation reports which master switches resolved on and what that makes required. - # It reads the environment and creates nothing, so it leaks no secret and opens no resource — - # and it is exposed precisely so a smoke lane can check the application's own answer rather - # than the flags the lane itself passed in, which is a test of the test. - include: health,prometheus,info,loggers,adapteractivation - # D2/D4/D5: explicitly excluded dangerous endpoints (env leaks secrets, - # heapdump/threaddump = memory forensics, shutdown = remote kill, configprops = secret leak). - exclude: env,configprops,heapdump,threaddump,shutdown - endpoint: - health: - # D8: never expose health details to unauthenticated callers. - show-details: when-authorized - # Membership is validated. With validation off, a group naming a contributor that does not - # exist is silently dropped and readiness reports UP while proving nothing about the - # dependency it claims to gate on — a false green that survives exactly as long as nobody - # checks. On, the cost is that every name below must resolve, in every deployment. - # - # That cost is paid by construction rather than by convention: the groups below name only - # unconditional contributors, and the one conditional member — `redisRequired` — is appended - # by RedisReadinessGroupPostProcessor from the same predicate that creates the bean. A - # contributor can therefore be named only where it exists, and a misspelled one still fails - # startup. - validate-group-membership: true - # feature-runtime-health-lifecycle-contract: expose the Kubernetes-ready - # liveness/readiness/startup probe paths. - probes: - enabled: true - group: - # Liveness: JVM can continue (OOM → liveness DOWN → pod restart). - # Only livenessState is included; dependency health MUST NOT be here — - # a DOWN DB must never trigger a pod restart when the JVM is healthy. - liveness: - include: livenessState - # Readiness: ready to serve traffic AND all REQUIRED dependencies up. - # Every name here must resolve to a contributor that exists (validate-group-membership - # above). A correctness-role Redis contributor belongs in this list and is added when the - # Redis runtime composition creates one; naming it before then produced a readiness probe - # that reported UP without ever checking Redis. - # `redisRequired` is NOT listed here, and must not be: group membership does not tolerate a - # conditional contributor being absent. Boot validates the group against the contributors - # that exist, so naming it statically made every Redis-off and cache-only deployment fail at - # startup with "Included health contributor 'redisRequired' in group 'readiness' does not - # exist". RedisReadinessGroupPostProcessor appends it to this list — from the same predicate - # that creates the bean — when Redis is on and a correctness role selected it. - # `redisOptional` is never appended: a cache outage is reported as degraded detail and never - # turns a healthy JVM or an otherwise-ready pod unavailable. - readiness: - include: readinessState - # Startup: startup/migration validation complete. - # readinessState acts as the startup completion gate — it flips UP only - # after the context is fully initialized (Flyway migration included). - startup: - include: readinessState - shutdown: - # D4: shutdown endpoint disabled globally — even if somehow exposed, it cannot be invoked. - access: none - info: - build: - # Build-info only (no env leak). - enabled: true - -server: - # integer 1-65535 - port: ${APP_SERVER_PORT:8080} - # graceful | immediate - shutdown: ${APP_SERVER_SHUTDOWN:graceful} - # none | native | framework (trust X-Forwarded-* behind LB/proxy) - forward-headers-strategy: ${APP_SERVER_FORWARD_HEADERS_STRATEGY:framework} - tomcat: - threads: - # integer >= 1 - max: ${APP_SERVER_TOMCAT_MAX_THREADS:200} - # integer >= 0 - min-spare: ${APP_SERVER_TOMCAT_MIN_SPARE_THREADS:10} - # integer >= 0 (OS-level backlog queue depth) - accept-count: ${APP_SERVER_TOMCAT_ACCEPT_COUNT:100} - # integer >= 1 (hard cap on simultaneously open connections) - max-connections: ${APP_SERVER_TOMCAT_MAX_CONNECTIONS:8192} - # duration: 20s | 1m - connection-timeout: ${APP_SERVER_TOMCAT_CONNECTION_TIMEOUT:20s} - compression: - # true | false - enabled: ${APP_SERVER_COMPRESSION_ENABLED:true} - # bytes or sized: 1024 | 1KB | 2KB - min-response-size: ${APP_SERVER_COMPRESSION_MIN_RESPONSE_SIZE:1KB} -logging: - level: - # TRACE | DEBUG | INFO | WARN | ERROR | OFF - # - # Inline defaults, because these had none and only application-local.yml pinned them. That made - # local the one environment that could start without an operator supplying every level: dev and - # prod failed to bind `logging.level.root` before reaching any of their own settings, which is a - # log-configuration problem wearing a startup failure's clothes. - root: ${APP_LOG_LEVEL_ROOT:INFO} - dev.caskeleton: ${APP_LOG_LEVEL_APP:INFO} - org.springframework: ${APP_LOG_LEVEL_SPRING:INFO} - org.springframework.web: ${APP_LOG_LEVEL_WEB:INFO} - # DEBUG here prints SQL once JPA/jdbc is wired in - org.hibernate.SQL: ${APP_LOG_LEVEL_SQL:INFO} - -# Module-scoped knobs. Each block is bound into a *Settings @ConfigurationProperties -# record in the corresponding module, which is where allowed-value validation lives. -ca-skeleton: - # The five optional adapters ship in this artifact and are off unless a deployment says otherwise, - # so one jar serves every environment and an operator turns a capability on with one variable. Off - # is structural: no settings bind, no bean is created, no socket, pool or thread exists, and a - # malformed detail setting for a capability nobody enabled does not block startup. - # Registry SSOT: docs/registries/env-keys.yaml. - persistence-jpa: - # true | false — relational persistence: DataSource, entity scan, repositories, Flyway. - enabled: ${APP_PERSISTENCE_JPA_ENABLED:false} - persistence-mongo: - # true | false - enabled: ${APP_PERSISTENCE_MONGO_ENABLED:false} - # Required non-blank when the switch above is true. Exactly one profile: the runtime builds one - # sync client and one pool, and never resolves a non-selected profile's secret. - active-profile: ${APP_PERSISTENCE_MONGO_ACTIVE_PROFILE:} - # Canonical HTTP capability activation. Bindings are the sole activation SSOT: provider - # definitions alone are inert, and the current NOT_IMPLEMENTED readiness card rejects ACTIVE - # before any client/executor/pool resource can be created. - capabilities: - # Every capability below renders its keys under one namespace — app.redis.namespace, which is - # {environment}:{service}:{domain}. The per-capability namespace-application / - # namespace-environment pairs that used to live here are gone: four capabilities each joining - # two free-form tokens in their own order produced four different key prefixes, and the ACL - # pattern meant to fence the deployment in matched none of them. - cache: - # Canonical semantic cache activation. Disabled by default; "redis" composes - # RedisCacheRegionAdapter for the region named below and resolves HMAC material by reference. - bindings: - default: ${APP_CACHE_CANONICAL_DEFAULT_PROVIDER:disabled} - semantic-region: ${APP_CACHE_REDIS_SEMANTIC_REGION:default} - key-version: 1 - key-hmac-secret-reference: secret://environment/APP_CACHE_REDIS_KEY_HMAC_SECRET - command-timeout: ${APP_CACHE_REDIS_COMMAND_TIMEOUT:200ms} - positive-soft-ttl: ${APP_CACHE_REDIS_POSITIVE_SOFT_TTL:30s} - positive-hard-ttl: ${APP_CACHE_REDIS_POSITIVE_HARD_TTL:5m} - negative-ttl: ${APP_CACHE_REDIS_NEGATIVE_TTL:10s} - # Floor on the hard TTL. Below it, entries expire faster than the round trip that wrote them: - # every read misses and every miss writes. - minimum-hard-ttl: ${APP_CACHE_REDIS_MINIMUM_HARD_TTL:1s} - idempotency: - # disabled | jdbc | redis. JDBC is the existing V1 provider; Redis is the owner-safe V2 - # provider. They are mutually exclusive and no V1-to-V2 facade is inferred. - provider: ${APP_IDEMPOTENCY_PROVIDER:disabled} - key-hmac-secret-reference: secret://environment/APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET - key-version: 1 - command-timeout: ${APP_IDEMPOTENCY_REDIS_COMMAND_TIMEOUT:200ms} - processing-lease: ${APP_IDEMPOTENCY_PROCESSING_LEASE:30s} - replay-ttl: ${APP_IDEMPOTENCY_TTL:24h} - failure-retention: ${APP_IDEMPOTENCY_FAILURE_RETENTION:24h} - response-codec-id: json-v2 - # Numeric: the owner-safe claim writes this into the record and compares it there. - policy-revision: 2 - lease: - # disabled | redis. This is EFFICIENCY_ONLY and never supplies fencing. - provider: ${APP_LEASE_PROVIDER:disabled} - key-hmac-secret-reference: secret://environment/APP_LEASE_REDIS_KEY_HMAC_SECRET - key-version: 1 - command-timeout: ${APP_LEASE_REDIS_COMMAND_TIMEOUT:200ms} - contention-retry-after: ${APP_LEASE_REDIS_CONTENTION_RETRY_AFTER:50ms} - # How much shorter than the server's TTL this holder considers its lease valid. The two - # clocks are not the same clock, and a holder that measured the full TTL locally would still - # believe it held a lease the server had already handed to somebody else. - drift-budget: ${APP_LEASE_REDIS_DRIFT_BUDGET:10ms} - rate-limit: - # disabled | redis. This is the sole outbound provider activation selector. - provider: ${APP_RATE_LIMIT_PROVIDER:disabled} - failure-policy: ${APP_RATE_LIMIT_FAILURE_POLICY:fail-closed} - default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default} - failure-retry-after: ${APP_RATE_LIMIT_FAILURE_RETRY_AFTER:100ms} - key-version: ${APP_RATE_LIMIT_KEY_VERSION:1} - command-timeout: ${APP_RATE_LIMIT_REDIS_COMMAND_TIMEOUT:200ms} - policies: - api-default: - revision: ${APP_RATE_LIMIT_POLICY_REVISION:v1} - algorithm: ${APP_RATE_LIMIT_ALGORITHM:sliding-counter} - limit: ${APP_RATE_LIMIT_LIMIT:100} - window: ${APP_RATE_LIMIT_WINDOW:1s} - capacity: ${APP_RATE_LIMIT_CAPACITY:100} - refill-tokens: ${APP_RATE_LIMIT_REFILL_TOKENS:100} - refill-period: ${APP_RATE_LIMIT_REFILL_PERIOD:1s} - maximum-cost: ${APP_RATE_LIMIT_MAXIMUM_COST:10} - cleanup-grace: ${APP_RATE_LIMIT_CLEANUP_GRACE:5s} - maximum-clock-regression: ${APP_RATE_LIMIT_MAXIMUM_CLOCK_REGRESSION:250ms} - security: - bootstrap: - # required, non-blank — startup fails if blank (see BootstrapSettings) - app-name: ${APP_NAME} - runtime: - # feature-env-driven-runtime-configuration D8: enforced at startup by - # StartupSafetyValidator. The two prod-unsafe toggles fail startup if true under - # the prod profile; multi-instance fails startup if its coordination beans are absent. - # true | false (internal error detail in responses — forbidden under prod) - error-detail-exposure-enabled: ${APP_ERROR_DETAIL_EXPOSURE_ENABLED:false} - # true | false (request/response body capture in logs — forbidden under prod) - log-body-capture-enabled: ${APP_LOG_BODY_CAPTURE_ENABLED:false} - # true | false (requires the 5 instance-coordination beans when true) - multi-instance-enabled: ${APP_MULTI_INSTANCE_ENABLED:false} - # true | false (whether to run Flyway migrations automatically at startup) - migration-on-startup: ${APP_MIGRATION_ON_STARTUP:true} - # feature-distributed-lock-contract D5 — distributed lock acquisition contract (try-lock + - # finite wait + lease TTL). Bound to LockSettings (adapter-persistence). Plain values, NOT - # APP_* env keys — new env-key registration is feature-env-driven-runtime-configuration's - # domain (out of scope for this branch); code defaults in LockSettings mirror these. - lock: - wait-time: 3s - lease-ttl: 30s - # Which RDBMS vendor composition the persistence adapter registers: postgresql | h2. Bound to - # PersistenceVendorSettings (adapter-persistence), which rejects any other value at startup. - # - # A property rather than a profile check, because the vendor belongs to the datastore and not to - # the environment that happens to use it. The profiles then state their choice: - # application-local.yml selects h2, application-dev.yml and application-prod.yml select - # postgresql, and prod additionally refuses h2 through PersistenceVendorProdSafetyValidator. - # - # The value here is the default for a deployment that activates no profile at all — PostgreSQL, - # the vendor every deployment ran before this selector existed. - # Notification delivery platform. Disabled by default, and disabled means nothing is bound: no - # provider runtime, no schema activation check, no scheduler thread, no secret required. Enabling - # it needs a real database with the notification schema stream applied and at least one provider - # profile with credentials — see docs/notification/configuration-reference.md, which is the - # reference this tree is kept in step with. - # - # Every value carries an inline default so that a deployment leaving the platform off never has to - # supply an environment variable for a subsystem it is not running. The tree existed only as - # relaxed-binding environment variables until 2026-08-15: a template user could not discover which - # profile, secret, callback and activation settings had to line up, because nothing wrote them - # down together. - notification: - platform: - # The master switch. False binds nothing at all. - enabled: ${APP_NOTIFICATION_PLATFORM_ENABLED:false} - # SERVING | INGEST_ONLY. SERVING refuses to start without a working provider; INGEST_ONLY - # stores requests and does not dispatch, which is the shape a migration or a drain wants. - mode: ${APP_NOTIFICATION_PLATFORM_MODE:SERVING} - dispatch: - claim-batch-size: ${APP_NOTIFICATION_PLATFORM_CLAIM_BATCH_SIZE:50} - # The lease must outlast a provider call plus its timeout, or a live worker's delivery is - # claimed by a second one while the first is still waiting on the provider. - lease-duration: ${APP_NOTIFICATION_PLATFORM_LEASE_DURATION:2m} - poll-interval: ${APP_NOTIFICATION_PLATFORM_POLL_INTERVAL:1s} - max-global-concurrency: ${APP_NOTIFICATION_PLATFORM_MAX_CONCURRENCY:64} - max-additional-attempts: ${APP_NOTIFICATION_PLATFORM_MAX_ADDITIONAL_ATTEMPTS:4} - max-queue-age: ${APP_NOTIFICATION_PLATFORM_MAX_QUEUE_AGE:24h} - # An ambiguous attempt means the request reached the provider and the outcome could not be - # read. Falling back would risk sending the same notification twice, so it is off. - allow-ambiguous-fallback: ${APP_NOTIFICATION_PLATFORM_ALLOW_AMBIGUOUS_FALLBACK:false} - callbacks: - enabled: ${APP_NOTIFICATION_PLATFORM_CALLBACKS_ENABLED:false} - # Ceiling is 65508 = the ciphertext column minus the AES-GCM nonce and tag. A larger value - # is refused at binding rather than at the database, after the callback was acknowledged. - max-body-bytes: ${APP_NOTIFICATION_PLATFORM_CALLBACK_MAX_BODY_BYTES:65508} - replay-skew: ${APP_NOTIFICATION_PLATFORM_CALLBACK_REPLAY_SKEW:5m} - # Peers whose Forwarded/X-Forwarded-* headers may be believed when reconstructing the URL a - # provider signed. Empty by default, and that is the safe default rather than the convenient - # one: with no entry the resolver uses what the container observed, so no caller can choose - # the URL its own signature is checked against. A deployment behind a load balancer lists its - # proxies here. - trusted-proxies: ${APP_NOTIFICATION_PLATFORM_CALLBACK_TRUSTED_PROXIES:} - # Provider profiles, keyed by profile id. None by default: an empty map with mode=SERVING is - # refused at startup, which is the intended outcome for "enabled but not configured". - # - # A profile looks like this — copy it under a profile id of your own and supply the secrets - # through the credential profile, never inline: - # - # providers: - # ses-primary: - # type: SES # APNS | FCM | SES | SMTP | TWILIO | WEB_PUSH | WEBHOOK - # enabled: true - # primary-for-channel: true # exactly one primary per channel - # environment: PRODUCTION - # credential-profile: ses-prod # resolved through SecretMaterialProvider - # timeout: 10s - # max-concurrency: 16 - # rate-per-second: 50 - # One shipped profile, disabled, because a map cannot be written by an environment variable. - # - # `ca-skeleton` contains a hyphen and an environment variable cannot express one — Spring maps - # `_` to `.`, never to `-` — so every setting in this tree reaches a deployment through an - # APP_ placeholder named here. A provider map with caller-chosen keys has no placeholder to be - # named by, which is why the tree shipped as `providers: {}` and no deployment could add one. - # - # So the template ships the reference family as one profile, off. A deployment that wants a - # different id or a second family declares it in its own YAML; a deployment that wants the - # common case sets APP_NOTIFICATION_PLATFORM_SMTP_ENABLED=true and the relay address below. - # - # Disabled profiles are skipped by assembly before their type is resolved, so this costs a - # deployment that does not want it exactly nothing. - providers: - smtp: - type: SMTP - enabled: ${APP_NOTIFICATION_PLATFORM_SMTP_ENABLED:false} - # Exactly one primary per channel; a second primary for EMAIL fails assembly by name. - primary-for-channel: ${APP_NOTIFICATION_PLATFORM_SMTP_PRIMARY:true} - environment: ${APP_NOTIFICATION_PLATFORM_SMTP_ENVIRONMENT:local} - credential-profile: ${APP_NOTIFICATION_PLATFORM_SMTP_CREDENTIAL_PROFILE:default} - timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_TIMEOUT:10s} - max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_MAX_CONCURRENCY:4} - rate-per-second: ${APP_NOTIFICATION_PLATFORM_SMTP_RATE_PER_SECOND:10} - # What the relay is, as opposed to which profile uses it. Host, port and credentials stay - # spring.mail.*; these are the parts Spring's mail properties have no word for. - smtp: - # STARTTLS_REQUIRED | IMPLICIT_TLS. There is no plaintext member: the type refuses an - # unencrypted relay by construction rather than by a validator somebody has to run. - tls-mode: ${APP_NOTIFICATION_PLATFORM_SMTP_TLS_MODE:STARTTLS_REQUIRED} - # example.invalid is reserved by RFC 2606 and resolves nowhere, so a deployment that forgot - # to set one gets a traceable bounce rather than mail apparently from an address it does not - # own. - sender-identity: ${APP_NOTIFICATION_PLATFORM_SMTP_SENDER_IDENTITY:no-reply@example.invalid} - connect-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_CONNECT_TIMEOUT:5s} - read-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_READ_TIMEOUT:10s} - write-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_WRITE_TIMEOUT:10s} - max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_DISPATCH_CONCURRENCY:4} - # ---- Key material ------------------------------------------------------- - # Eight purposes, eight keys, each base64 of at least 32 bytes and each distinct from every - # other. The platform decodes them at startup and refuses to boot if one is blank, short or - # shared: a blank value used to be skipped, so the platform started without the key and found - # out on the first contact point — in production, on a user's notification. - # - # These were bound by nothing until now. The subtree was missing from this file entirely while - # the settings record and its validator existed, so `enabled: true` was an unconditional boot - # failure in every environment and the only way to discover the eight names was to read the - # record. That is the gap this block closes; the empty defaults keep a deployment with the - # platform off from having to supply anything. - # - # Never write a value here. Supply them out of band, per environment. - secrets: - contact-encryption-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY:} - contact-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY:} - callback-signing-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY:} - provider-credential-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY:} - payload-encryption-key: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY:} - vapid-signing-key: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY:} - provider-request-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY:} - callback-fingerprint-hmac-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY:} - # The id written into every envelope this key produces, one per purpose, keyed by the - # purpose's own name. Not secret — an id identifies key material without revealing it — but - # required all the same, and with no default on purpose: a constant id makes a rotation - # indistinguishable from the key it replaced, so nothing could decrypt what was written - # before it. Change the id in the same deployment that changes the material. - active-key-ids: - CONTACT_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY_ID:} - CONTACT_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY_ID:} - CALLBACK_SIGNING: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY_ID:} - PROVIDER_CREDENTIAL: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY_ID:} - PAYLOAD_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY_ID:} - VAPID_SIGNING: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY_ID:} - PROVIDER_REQUEST_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY_ID:} - CALLBACK_FINGERPRINT_HMAC: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY_ID:} - # Superseded material, kept readable, grouped by the purpose it served and then by the id - # it was active under — historical-keys.PAYLOAD_ENCRYPTION.payload-2026-01. The purpose is - # not recoverable from an id, and every consumer that resolves a key by id refuses one - # carrying the wrong purpose, so a retired key filed under the wrong heading resolves and - # is then rejected. Unset until a first rotation; a deployment that has never rotated needs - # none. See docs/notification/at-rest-threat-model.md for the rotation sequence. - historical-keys: {} - - persistence: - vendor: postgresql - # JPA named-policy deadline envelope. JpaTransactionSettings validates the hierarchy; - # SpringPolicyTransactionPort intersects these limits with the caller's absolute CallBudget - # and the actual Hikari connection timeout before acquiring a transaction. - jpa: - transaction: - transaction-timeout: 30s - begin-budget: 250ms - minimum-action-window: 1s - completion-margin: 500ms - statement-timeout: 10s - lock-timeout: 2s - idle-guard-timeout: 15s - transaction-margin: 250ms - lock-margin: 100ms - retry-base-delay: 10ms - retry-maximum-delay: 50ms - retry-maximum-attempts: 2 - presentation: - # feature-api-contract-baseline D2: API version prefix. Default is the URI - # prefix "/v1" (major-version path, AIP-185); override via env, or set "" for - # no prefix. The supplemental "X-Api-Version" header never overrides the path. - api-base-path: ${PRESENTATION_API_BASE_PATH:/v1} - idempotency: - # feature-rate-limit-idempotency-contract D6/§E. ttl is env-driven (<=72h, - # validated in IdempotencyProperties); reaper-interval is literal operational tuning. - ttl: ${APP_IDEMPOTENCY_TTL:24h} - reaper-interval: 10m - async: - # feature-background-job-async-contract D7 — @Async ThreadPoolTaskExecutor pool sizing. - # Registry SSOT: docs/registries/env-keys.yaml (APP_ASYNC_EXECUTOR_* rows 1337-1377). - # Bound into AsyncExecutorSettings @ConfigurationProperties(prefix = "ca-skeleton.async.executor"). - # Bounded queue is mandatory (D7) — an unbounded queue makes max-size unreachable. - executor: - # int >= 1 (positive_int). Always-alive worker count. - core-size: ${APP_ASYNC_EXECUTOR_CORE_SIZE:10} - # int >= core-size (positive_int_ge_core). Hard ceiling on workers. - max-size: ${APP_ASYNC_EXECUTOR_MAX_SIZE:50} - # int in 1..= 1 — maximum rows claimed per relay cycle - batch-size: 20 - # ISO-8601 duration — IN_FLIGHT orphan visibility window (I6: next_attempt_at reuse) - in-flight-timeout: PT5M - # ISO-8601 duration — read also by adapter-persistence OutboxReaper @Scheduled - reaper-interval: PT10M - # ISO-8601 duration — PUBLISHED row retention before reaper deletes them (I3) - # read also by adapter-persistence OutboxReaper via ${ca-skeleton.outbox.published-retention:P7D} - published-retention: P7D - security: - # jwt | redis-session; the bootstrap composition validator rejects mixed infrastructure. - auth-mode: ${APP_SECURITY_AUTH_MODE:jwt} - # Required only in jwt mode. - issuer-uri: ${APP_SECURITY_JWT_ISSUER} - # blank to skip audience check - audience: ${APP_SECURITY_JWT_AUDIENCE} - # comma-separated list (Spring binds to List) - # - # Derived from the API base path, not restated. The default here was the literal - # /api/healthcheck while presentation.api-base-path defaults to /v1, so the two shipped defaults - # described one address and disagreed about it: the health endpoint was published at - # /v1/healthcheck and the allowlist opened /api/healthcheck, a path no handler serves. The - # public health endpoint was therefore not public in any runtime except local, which pins both - # to /api and so never saw it. Found by the shared-infra-dev lane, where the smoke client got a - # refusal from an application that had started perfectly. - public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck} - session: - cookie-name: ${APP_SESSION_COOKIE_NAME:CA_SESSION} - secure: ${APP_SESSION_COOKIE_SECURE:true} - http-only: ${APP_SESSION_COOKIE_HTTP_ONLY:true} - same-site: ${APP_SESSION_COOKIE_SAME_SITE:Lax} - path: ${APP_SESSION_COOKIE_PATH:/} - csrf-cookie-name: ${APP_SESSION_CSRF_COOKIE_NAME:XSRF-TOKEN} - csrf-header-name: ${APP_SESSION_CSRF_HEADER_NAME:X-XSRF-TOKEN} - authz: - # feature-authentication-authorization-contract D2/D3/D8: app-side role→permission - # mapping (the default source; IdP-issued permission claims are an out-of-scope - # alternative). Keys are RAW IdP role names (no ROLE_ prefix — that prefix only - # exists on Spring authorities, not on the principal's raw role set), looked up - # case-insensitively. Values are explicitly enumerated `resource:action` permissions - # (no wildcards — least-privilege, OWASP-AUTHZ-C4). The values below are the - # sample-portfolio demonstration (§5): `user` may read/write, only `admin` may close. - role-permissions: - user: worklog:read,worklog:write - admin: worklog:read,worklog:write,worklog:close - # The notification platform's own two permissions, granted to two roles rather than one. - # Publishing a template changes what every future submission renders, so a service that may - # send notifications is not thereby a service that may rewrite them; a deployment that wants - # both grants both roles. - notification-submit: notification:submit - notification-template-publish: notification-template:publish - cors: - # true | false - enabled: ${APP_SECURITY_CORS_ENABLED:false} - # comma-separated. Empty by default, unlike the other undefaulted values: CORS is off by - # default, and an empty origin list is the safest possible value rather than an arbitrary one. - # The rule those others follow — no default for an address, a credential, or an identity — is - # about not running against something nobody chose; allowing nobody is a choice. - allowed-origins: ${APP_SECURITY_CORS_ORIGINS:} - # comma-separated; empty -> defaults - allowed-methods: ${APP_SECURITY_CORS_ALLOWED_METHODS:} - # comma-separated; "*" allows any - allowed-headers: ${APP_SECURITY_CORS_ALLOWED_HEADERS:*} - # true | false - allow-credentials: ${APP_SECURITY_CORS_ALLOW_CREDENTIALS:false} - # seconds - max-age-seconds: ${APP_SECURITY_CORS_MAX_AGE:600} - logging: - file: - # true | false (wraps console + adds rolling JSON file appender) - enabled: ${APP_LOG_FILE_ENABLED:false} - # relative (to src/) or absolute - path: ${APP_LOG_FILE_PATH:logs/ca-skeleton.json} - # size with unit: KB | MB | GB - max-size: ${APP_LOG_FILE_MAX_SIZE:100MB} - # integer >= 1 - max-history: ${APP_LOG_FILE_MAX_HISTORY:14} - # size with unit or 0 - total-size-cap: ${APP_LOG_FILE_TOTAL_SIZE_CAP:3GB} - async: - # true | false (AsyncAppender wrapper for non-blocking I/O) - enabled: ${APP_LOG_ASYNC_ENABLED:true} - # integer >= 1 - queue-size: ${APP_LOG_ASYNC_QUEUE_SIZE:512} - # integer >= 0 (0 = never drop) - discarding-threshold: ${APP_LOG_ASYNC_DISCARDING_THRESHOLD:20} - json: - # IANA timezone (UTC | Asia/Seoul | ...) or "default" - timezone: ${APP_LOG_JSON_TIMEZONE:UTC} - # ISO 8601 pattern - timestamp-pattern: ${APP_LOG_JSON_TIMESTAMP_PATTERN:yyyy-MM-dd'T'HH:mm:ss.SSSXXX} - # true | false (file/method/line — expensive) - include-caller-data: ${APP_LOG_JSON_INCLUDE_CALLER_DATA:false} - # integer; 0 = full name, positive = abbreviated - logger-name-length: ${APP_LOG_JSON_LOGGER_NAME_LENGTH:0} - # float in [0.0, 1.0] — keep-probability for <=INFO logs (prod 0.1 = 10% sampling; - # WARN/ERROR always 100%). Consumed by SamplingTurboFilter via logback . - sampling-rate: ${APP_LOG_SAMPLING_RATE:1.0} - privacy: - # secret-tier HMAC salt for user_principal pseudonymization (DRIFT-6; algorithm SSOT - # feature-data-retention-privacy-contract). Blank -> PrivacySettings warns + uses a dev - # sentinel; prod MUST supply a real secret-manager value. - pseudonymization-salt: ${APP_PRIVACY_PSEUDONYMIZATION_SALT:} - # feature-distributed-tracing-contract D1/D4/D6 — tracing seam settings. - # The OTel/Micrometer tracer runtime is active in this repo (Plan A — seam activated): - # micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp are wired in - # app-bootstrap/build.gradle. The exporter remains off while OTEL_EXPORTER_OTLP_ENDPOINT - # is blank (url_or_empty default). A fork can supply the endpoint via env or override - # SpanErrorRecorder with its own bean. - # D4 disabled-fallback: even when enabled=false, RequestLoggingFilter always - # generates a W3C traceparent so meta.traceId and log trace_id are never null. - # D6 per-profile defaults: prod=0.01 / staging=0.10 / dev·local=1.0. - # APP_TRACING_SAMPLE_RATE overrides the per-profile default when set. - tracing: - # true | false (boolean_strict). Tracing seam on/off. - enabled: ${APP_TRACING_ENABLED:true} - # float in [0.0, 1.0]. Per-profile override (D6 float_between_0_and_1 validation at startup). - # blank = per-profile default via TracingSampleRateResolver (D-1 ISSUE-1 fix). - sample-rate: ${APP_TRACING_SAMPLE_RATE:} - exporter: - # url_or_empty: blank = exporter off (D1 SEAM default); non-blank must be a valid URL. - otlp-endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:} - -# feature-integration-adapter-templates — optional integration adapter toggles. -# The `app...enabled` flags feed Spring @ConditionalOnProperty (Layer 1): -# disabled (default): messaging binds a fail-fast Disabled* sentinel bean; cache/notification -# contribute nothing and fail fast in their router (CacheStoreRouter / RoutingNotifier) on -# unbound access. Enabled registers the real adapter -# (which needs its project-supplied integration client bean). Domain namespace, NOT a -# generic `app.adapter.*` prefix (branch-note §Audit A1). Env keys are the registry SSOT. -app: - # The one global Redis switch. False (the default) means no Redis settings are bound, no Redis - # credential is required, no client, connection, thread or health contributor is created, and no - # Redis-specific resource is read. There is deliberately no second master switch: a role such as - # cache or session selects *which* Redis capabilities compose, never *whether* Redis exists. - redis: - enabled: ${APP_REDIS_ENABLED:false} - # Fileserver R2 exact destination/provider composition. Disabled by default: while false, - # these blank attestation placeholders do not create directories, probe a filesystem, or - # contribute FilePublicationPort. Enabling fails closed unless every local-persistent - # attestation value matches the pre-provisioned root. No implicit local fallback exists. - fileserver: - enabled: ${APP_FILESERVER_ENABLED:false} - destinations: - local-export: - provider-ref: local-primary - required-publication: unique-atomic-create - required-durability: file-and-directory-sync - maximum-rows: 1000000 - maximum-encoded-bytes: 1073741824 - providers: - local-primary: - # local-persistent is the only implemented/qualified R2 provider. - # shared-mounted/NFS and SFTP settings must not be added before their providers exist. - type: local-persistent - root-directory: ${APP_FILESERVER_LOCAL_ROOT:} - auto-create: false - strict-path-security: true - expected-file-store-name: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME:} - expected-file-store-type: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE:} - mount-sentinel-name: .ca-fileserver-volume - mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:} - expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:} - maximum-root-mode: "0750" - # HTTP Fileserver platform — a different capability from app.fileserver above, which publishes - # tabular exports. This one serves upload/download/lifecycle routes over HTTP and owns its own - # namespace so the two cannot be switched on by accident together. - # - # While enabled=false none of the detail below is bound: FileserverPlatformAutoConfiguration - # binds the block itself, and it is not processed until the master switch is true. Unknown keys - # under this prefix are refused rather than ignored. - # Outbound HTTP Client platform. Only the master switch lives here. - # - # The per-client surface is an indexed list, and templating one element would materialise a - # nameless client in every deployment — which the settings' own aggregate validation refuses, so - # the template could not be left in place. Clients are therefore declared straight from the - # environment as APP_HTTPCLIENT_CLIENTS_0_*, registered in - # docs/httpclient/env-fields.yaml, and an APP_HTTPCLIENT_ variable absent from that - # registry fails startup rather than being ignored. - # - # While enabled=false none of it is bound: HttpClientPlatformAutoConfiguration binds the block - # itself and is not processed until the master switch is true. - httpclient: - enabled: ${APP_HTTPCLIENT_ENABLED:false} - fileserver-platform: - enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false} - # Writer-lease owner. Must be unique per instance in a multi-instance deployment; the startup - # gate treats the default as single-instance. - instance-id: ${APP_FILESERVER_PLATFORM_INSTANCE_ID:local-node} - default-namespace: ${APP_FILESERVER_PLATFORM_DEFAULT_NAMESPACE:default} - storage: - # Absolute, on its own volume, and never under a web or configuration root. A relative path - # resolves against the process working directory, which differs between a container and a - # test, so it is refused. - root: ${APP_FILESERVER_PLATFORM_STORAGE_ROOT:/var/lib/backend/files} - publish-mode: ${APP_FILESERVER_PLATFORM_STORAGE_PUBLISH_MODE:atomic-move-preferred} - buffer-size: ${APP_FILESERVER_PLATFORM_STORAGE_BUFFER_SIZE:128KB} - forbidden-root-ancestors: ${APP_FILESERVER_PLATFORM_STORAGE_FORBIDDEN_ROOT_ANCESTORS:/app,/etc,/usr/share/nginx/html} - upload: - # Shared with spring.servlet.multipart below through the same placeholder. Two independent - # limits would let the servlet container reject an upload the Fileserver policy allows, - # before any Fileserver code — including its error mapping — ever runs. - max-file-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_FILE_SIZE:100MB} - max-request-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_REQUEST_SIZE:110MB} - initial-reservation: ${APP_FILESERVER_PLATFORM_UPLOAD_INITIAL_RESERVATION:8MB} - max-parts: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_PARTS:16} - ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_TTL:1h} - reservation-ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_RESERVATION_TTL:24h} - lease-duration: ${APP_FILESERVER_PLATFORM_UPLOAD_LEASE_DURATION:30s} - require-content-length: ${APP_FILESERVER_PLATFORM_UPLOAD_REQUIRE_CONTENT_LENGTH:false} - download: - cache-control: ${APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL:private, no-store} - inline-allowed: ${APP_FILESERVER_PLATFORM_DOWNLOAD_INLINE_ALLOWED:false} - max-ranges: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGES:1} - # Applies to the single-range profile too, so the ceiling is not inert by default. - max-range-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGE_BYTES:100MB} - zero-copy-enabled: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_ENABLED:true} - zero-copy-minimum-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_MINIMUM_BYTES:16MB} - transfer: - core-size: ${APP_FILESERVER_PLATFORM_TRANSFER_CORE_SIZE:8} - max-size: ${APP_FILESERVER_PLATFORM_TRANSFER_MAX_SIZE:32} - queue-capacity: ${APP_FILESERVER_PLATFORM_TRANSFER_QUEUE_CAPACITY:64} - await-seconds: ${APP_FILESERVER_PLATFORM_TRANSFER_AWAIT_SECONDS:300} - security: - # required | role-based | unenforced. `required` has no built-in policy and fails startup - # unless the deployment supplies a FileAccessPolicy bean; `unenforced` is refused under a - # production profile. - access-policy: ${APP_FILESERVER_PLATFORM_SECURITY_ACCESS_POLICY:required} - read-roles: ${APP_FILESERVER_PLATFORM_SECURITY_READ_ROLES:ROLE_FILE_READ} - write-roles: ${APP_FILESERVER_PLATFORM_SECURITY_WRITE_ROLES:ROLE_FILE_WRITE} - admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN} - verification: - timeout: ${APP_FILESERVER_PLATFORM_VERIFICATION_TIMEOUT:5s} - require-media-type-verdict: ${APP_FILESERVER_PLATFORM_VERIFICATION_REQUIRE_MEDIA_TYPE_VERDICT:false} - inline-safe-profile: ${APP_FILESERVER_PLATFORM_VERIFICATION_INLINE_SAFE_PROFILE:false} - quota: - instance-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_INSTANCE_UPLOAD_PERMITS:16} - scope-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_SCOPE_UPLOAD_PERMITS:4} - direct-download-permits: ${APP_FILESERVER_PLATFORM_QUOTA_DIRECT_DOWNLOAD_PERMITS:64} - soft-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_SOFT_HIGH_WATER:0.70} - hard-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_HARD_HIGH_WATER:0.85} - admin: - enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false} - orphan-minimum-age: ${APP_FILESERVER_PLATFORM_ADMIN_ORPHAN_MINIMUM_AGE:1h} - cleanup: - enabled: ${APP_FILESERVER_PLATFORM_CLEANUP_ENABLED:false} - interval: ${APP_FILESERVER_PLATFORM_CLEANUP_INTERVAL:60s} - max-items: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_ITEMS:100} - max-bytes: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_BYTES:1GB} - retry-backoff: ${APP_FILESERVER_PLATFORM_CLEANUP_RETRY_BACKOFF:5m} - tus: - enabled: ${APP_FILESERVER_PLATFORM_TUS_ENABLED:false} - httpbis-draft12: - # Unratified protocol; the contract can change without notice. - enabled: ${APP_FILESERVER_PLATFORM_HTTPBIS_DRAFT12_ENABLED:false} - nginx: - enabled: ${APP_FILESERVER_PLATFORM_NGINX_ENABLED:false} - internal-prefix: ${APP_FILESERVER_PLATFORM_NGINX_INTERNAL_PREFIX:/__files/} - object-suffix: ${APP_FILESERVER_PLATFORM_NGINX_OBJECT_SUFFIX:.bin} - minimum-size: ${APP_FILESERVER_PLATFORM_NGINX_MINIMUM_SIZE:16MB} - observability: - metrics-enabled: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_METRICS_ENABLED:true} - # Secret. Keyed HMAC over file identifiers; an unkeyed digest of an enumerable id is - # reversible, so startup fails while metrics are on and this is blank. - fingerprint-key: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_FINGERPRINT_KEY:} - rate-limit: - # Inbound HTTP enforcement is a separate axis from outbound provider activation. - # enabled=true with no exact EdgeRateLimitPort fails fast; it never installs a local fallback. - enabled: ${APP_RATE_LIMIT_ENABLED:false} - default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default} - hash-key-version: ${APP_RATE_LIMIT_HASH_KEY_VERSION:1} - caller-deadline-budget: 2s - # remote-addr-only | forwarded-headers-trusted (trusted ingress only) - client-ip-mode: ${APP_RATE_LIMIT_CLIENT_IP_MODE:remote-addr-only} - # Logical-cache-name → backendId routing (CacheStoreRouter). No keys by default — - # forks add e.g. `bindings: { worklog: redis }` or env APP_CACHE_BINDINGS_WORKLOG=redis. - # A binding to a backend that is not enabled fails startup (Layer 3 moved to router). - messaging: - # true | false — whether there is a broker at all. `broker` below selects which one; a - # blank broker used to be the de-facto switch, which is a selector doing a switch's job. - enabled: ${APP_MESSAGING_ENABLED:false} - # Active message broker id (e.g. kafka). Blank = messaging disabled (fail-fast on use). - # Selects the single MessageBroker; adding a broker is new files only (MessagingConfig). - broker: ${APP_MESSAGING_BROKER:} - kafka: - # CSV of host:port; required (non-empty) only when broker=kafka. Bound + - # validated by KafkaAdapterSettings (format) + KafkaAdapterConfig (required-when-active). - brokers: ${APP_MESSAGING_KAFKA_BROKERS:} - notification: - # Active provider id per kind; blank = that kind disabled (fail-fast on use). - # Add a provider = new files only (NotificationConfig); select it here. - # This is the R0 legacy seam. The delivery platform is the separate - # ca-skeleton.notification.platform tree below. - slack: - provider: ${APP_NOTIFICATION_SLACK_PROVIDER:} - email: - provider: ${APP_NOTIFICATION_EMAIL_PROVIDER:} - -backend: - graphql: - # true | false — publishes /graphql, including the route Spring GraphQL would publish itself. - enabled: ${APP_GRAPHQL_ENABLED:false} - # Required when the switch above is true, and must match the runtime environment - # (local->LOCAL, dev->DEV, prod->PRODUCTION_INTERNAL|PRODUCTION_PUBLIC). Replaces the - # production boolean and environment enum, which could disagree with each other. - deployment-mode: ${APP_GRAPHQL_DEPLOYMENT_MODE:} + config: + import: + - classpath:config/server.yml + - classpath:config/observability.yml + - classpath:config/persistence.yml + - classpath:config/capabilities.yml + - classpath:config/runtime.yml + - classpath:config/notification.yml + - classpath:config/idempotency.yml + - classpath:config/outbox.yml + - classpath:config/security.yml + - classpath:config/redis.yml + - classpath:config/fileserver.yml + - classpath:config/httpclient.yml + - classpath:config/ratelimit.yml + - classpath:config/messaging.yml + - classpath:config/graphql.yml diff --git a/src/app-bootstrap/src/main/resources/config/capabilities.yml b/src/app-bootstrap/src/main/resources/config/capabilities.yml new file mode 100644 index 00000000..e2dc450f --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/capabilities.yml @@ -0,0 +1,71 @@ +# 능력별 제공자 선택(cache/rate-limit/lease/idempotency). +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +ca-skeleton: + capabilities: + # Every capability below renders its keys under one namespace — app.redis.namespace, which is + # {environment}:{service}:{domain}. The per-capability namespace-application / + # namespace-environment pairs that used to live here are gone: four capabilities each joining + # two free-form tokens in their own order produced four different key prefixes, and the ACL + # pattern meant to fence the deployment in matched none of them. + cache: + # Canonical semantic cache activation. Disabled by default; "redis" composes + # RedisCacheRegionAdapter for the region named below and resolves HMAC material by reference. + bindings: + default: ${APP_CACHE_CANONICAL_DEFAULT_PROVIDER:disabled} + semantic-region: ${APP_CACHE_REDIS_SEMANTIC_REGION:default} + key-version: 1 + key-hmac-secret-reference: secret://environment/APP_CACHE_REDIS_KEY_HMAC_SECRET + command-timeout: ${APP_CACHE_REDIS_COMMAND_TIMEOUT:200ms} + positive-soft-ttl: ${APP_CACHE_REDIS_POSITIVE_SOFT_TTL:30s} + positive-hard-ttl: ${APP_CACHE_REDIS_POSITIVE_HARD_TTL:5m} + negative-ttl: ${APP_CACHE_REDIS_NEGATIVE_TTL:10s} + # Floor on the hard TTL. Below it, entries expire faster than the round trip that wrote them: + # every read misses and every miss writes. + minimum-hard-ttl: ${APP_CACHE_REDIS_MINIMUM_HARD_TTL:1s} + idempotency: + # disabled | jdbc | redis. JDBC is the existing V1 provider; Redis is the owner-safe V2 + # provider. They are mutually exclusive and no V1-to-V2 facade is inferred. + provider: ${APP_IDEMPOTENCY_PROVIDER:disabled} + key-hmac-secret-reference: secret://environment/APP_IDEMPOTENCY_REDIS_KEY_HMAC_SECRET + key-version: 1 + command-timeout: ${APP_IDEMPOTENCY_REDIS_COMMAND_TIMEOUT:200ms} + processing-lease: ${APP_IDEMPOTENCY_PROCESSING_LEASE:30s} + replay-ttl: ${APP_IDEMPOTENCY_TTL:24h} + failure-retention: ${APP_IDEMPOTENCY_FAILURE_RETENTION:24h} + response-codec-id: json-v2 + # Numeric: the owner-safe claim writes this into the record and compares it there. + policy-revision: 2 + lease: + # disabled | redis. This is EFFICIENCY_ONLY and never supplies fencing. + provider: ${APP_LEASE_PROVIDER:disabled} + key-hmac-secret-reference: secret://environment/APP_LEASE_REDIS_KEY_HMAC_SECRET + key-version: 1 + command-timeout: ${APP_LEASE_REDIS_COMMAND_TIMEOUT:200ms} + contention-retry-after: ${APP_LEASE_REDIS_CONTENTION_RETRY_AFTER:50ms} + # How much shorter than the server's TTL this holder considers its lease valid. The two + # clocks are not the same clock, and a holder that measured the full TTL locally would still + # believe it held a lease the server had already handed to somebody else. + drift-budget: ${APP_LEASE_REDIS_DRIFT_BUDGET:10ms} + rate-limit: + # disabled | redis. This is the sole outbound provider activation selector. + provider: ${APP_RATE_LIMIT_PROVIDER:disabled} + failure-policy: ${APP_RATE_LIMIT_FAILURE_POLICY:fail-closed} + default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default} + failure-retry-after: ${APP_RATE_LIMIT_FAILURE_RETRY_AFTER:100ms} + key-version: ${APP_RATE_LIMIT_KEY_VERSION:1} + command-timeout: ${APP_RATE_LIMIT_REDIS_COMMAND_TIMEOUT:200ms} + policies: + api-default: + revision: ${APP_RATE_LIMIT_POLICY_REVISION:v1} + algorithm: ${APP_RATE_LIMIT_ALGORITHM:sliding-counter} + limit: ${APP_RATE_LIMIT_LIMIT:100} + window: ${APP_RATE_LIMIT_WINDOW:1s} + capacity: ${APP_RATE_LIMIT_CAPACITY:100} + refill-tokens: ${APP_RATE_LIMIT_REFILL_TOKENS:100} + refill-period: ${APP_RATE_LIMIT_REFILL_PERIOD:1s} + maximum-cost: ${APP_RATE_LIMIT_MAXIMUM_COST:10} + cleanup-grace: ${APP_RATE_LIMIT_CLEANUP_GRACE:5s} + maximum-clock-regression: ${APP_RATE_LIMIT_MAXIMUM_CLOCK_REGRESSION:250ms} + security: diff --git a/src/app-bootstrap/src/main/resources/config/fileserver.yml b/src/app-bootstrap/src/main/resources/config/fileserver.yml new file mode 100644 index 00000000..5be8547c --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/fileserver.yml @@ -0,0 +1,127 @@ +# 파일 전송 경계와 Fileserver 플랫폼. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +app: + fileserver: + enabled: ${APP_FILESERVER_ENABLED:false} + destinations: + local-export: + provider-ref: local-primary + required-publication: unique-atomic-create + required-durability: file-and-directory-sync + maximum-rows: 1000000 + maximum-encoded-bytes: 1073741824 + providers: + local-primary: + # local-persistent is the only implemented/qualified R2 provider. + # shared-mounted/NFS and SFTP settings must not be added before their providers exist. + type: local-persistent + root-directory: ${APP_FILESERVER_LOCAL_ROOT:} + auto-create: false + strict-path-security: true + expected-file-store-name: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_NAME:} + expected-file-store-type: ${APP_FILESERVER_LOCAL_EXPECTED_FILE_STORE_TYPE:} + mount-sentinel-name: .ca-fileserver-volume + mount-sentinel-sha256: ${APP_FILESERVER_LOCAL_MOUNT_SENTINEL_SHA256:} + expected-owner: ${APP_FILESERVER_LOCAL_EXPECTED_OWNER:} + maximum-root-mode: "0750" + # HTTP Fileserver platform — a different capability from app.fileserver above, which publishes + # tabular exports. This one serves upload/download/lifecycle routes over HTTP and owns its own + # namespace so the two cannot be switched on by accident together. + # + # While enabled=false none of the detail below is bound: FileserverPlatformAutoConfiguration + # binds the block itself, and it is not processed until the master switch is true. Unknown keys + # under this prefix are refused rather than ignored. + # Outbound HTTP Client platform. Only the master switch lives here. + # + # The per-client surface is an indexed list, and templating one element would materialise a + # nameless client in every deployment — which the settings' own aggregate validation refuses, so + # the template could not be left in place. Clients are therefore declared straight from the + # environment as APP_HTTPCLIENT_CLIENTS_0_*, registered in + # docs/httpclient/env-fields.yaml, and an APP_HTTPCLIENT_ variable absent from that + # registry fails startup rather than being ignored. + # + # While enabled=false none of it is bound: HttpClientPlatformAutoConfiguration binds the block + # itself and is not processed until the master switch is true. + fileserver-platform: + enabled: ${APP_FILESERVER_PLATFORM_ENABLED:false} + # Writer-lease owner. Must be unique per instance in a multi-instance deployment; the startup + # gate treats the default as single-instance. + instance-id: ${APP_FILESERVER_PLATFORM_INSTANCE_ID:local-node} + default-namespace: ${APP_FILESERVER_PLATFORM_DEFAULT_NAMESPACE:default} + storage: + # Absolute, on its own volume, and never under a web or configuration root. A relative path + # resolves against the process working directory, which differs between a container and a + # test, so it is refused. + root: ${APP_FILESERVER_PLATFORM_STORAGE_ROOT:/var/lib/backend/files} + publish-mode: ${APP_FILESERVER_PLATFORM_STORAGE_PUBLISH_MODE:atomic-move-preferred} + buffer-size: ${APP_FILESERVER_PLATFORM_STORAGE_BUFFER_SIZE:128KB} + forbidden-root-ancestors: ${APP_FILESERVER_PLATFORM_STORAGE_FORBIDDEN_ROOT_ANCESTORS:/app,/etc,/usr/share/nginx/html} + upload: + # Shared with spring.servlet.multipart below through the same placeholder. Two independent + # limits would let the servlet container reject an upload the Fileserver policy allows, + # before any Fileserver code — including its error mapping — ever runs. + max-file-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_FILE_SIZE:100MB} + max-request-size: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_REQUEST_SIZE:110MB} + initial-reservation: ${APP_FILESERVER_PLATFORM_UPLOAD_INITIAL_RESERVATION:8MB} + max-parts: ${APP_FILESERVER_PLATFORM_UPLOAD_MAX_PARTS:16} + ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_TTL:1h} + reservation-ttl: ${APP_FILESERVER_PLATFORM_UPLOAD_RESERVATION_TTL:24h} + lease-duration: ${APP_FILESERVER_PLATFORM_UPLOAD_LEASE_DURATION:30s} + require-content-length: ${APP_FILESERVER_PLATFORM_UPLOAD_REQUIRE_CONTENT_LENGTH:false} + download: + cache-control: ${APP_FILESERVER_PLATFORM_DOWNLOAD_CACHE_CONTROL:private, no-store} + inline-allowed: ${APP_FILESERVER_PLATFORM_DOWNLOAD_INLINE_ALLOWED:false} + max-ranges: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGES:1} + # Applies to the single-range profile too, so the ceiling is not inert by default. + max-range-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_MAX_RANGE_BYTES:100MB} + zero-copy-enabled: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_ENABLED:true} + zero-copy-minimum-bytes: ${APP_FILESERVER_PLATFORM_DOWNLOAD_ZERO_COPY_MINIMUM_BYTES:16MB} + transfer: + core-size: ${APP_FILESERVER_PLATFORM_TRANSFER_CORE_SIZE:8} + max-size: ${APP_FILESERVER_PLATFORM_TRANSFER_MAX_SIZE:32} + queue-capacity: ${APP_FILESERVER_PLATFORM_TRANSFER_QUEUE_CAPACITY:64} + await-seconds: ${APP_FILESERVER_PLATFORM_TRANSFER_AWAIT_SECONDS:300} + security: + # required | role-based | unenforced. `required` has no built-in policy and fails startup + # unless the deployment supplies a FileAccessPolicy bean; `unenforced` is refused under a + # production profile. + access-policy: ${APP_FILESERVER_PLATFORM_SECURITY_ACCESS_POLICY:required} + read-roles: ${APP_FILESERVER_PLATFORM_SECURITY_READ_ROLES:ROLE_FILE_READ} + write-roles: ${APP_FILESERVER_PLATFORM_SECURITY_WRITE_ROLES:ROLE_FILE_WRITE} + admin-roles: ${APP_FILESERVER_PLATFORM_SECURITY_ADMIN_ROLES:ROLE_FILE_ADMIN} + verification: + timeout: ${APP_FILESERVER_PLATFORM_VERIFICATION_TIMEOUT:5s} + require-media-type-verdict: ${APP_FILESERVER_PLATFORM_VERIFICATION_REQUIRE_MEDIA_TYPE_VERDICT:false} + inline-safe-profile: ${APP_FILESERVER_PLATFORM_VERIFICATION_INLINE_SAFE_PROFILE:false} + quota: + instance-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_INSTANCE_UPLOAD_PERMITS:16} + scope-upload-permits: ${APP_FILESERVER_PLATFORM_QUOTA_SCOPE_UPLOAD_PERMITS:4} + direct-download-permits: ${APP_FILESERVER_PLATFORM_QUOTA_DIRECT_DOWNLOAD_PERMITS:64} + soft-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_SOFT_HIGH_WATER:0.70} + hard-high-water: ${APP_FILESERVER_PLATFORM_QUOTA_HARD_HIGH_WATER:0.85} + admin: + enabled: ${APP_FILESERVER_PLATFORM_ADMIN_ENABLED:false} + orphan-minimum-age: ${APP_FILESERVER_PLATFORM_ADMIN_ORPHAN_MINIMUM_AGE:1h} + cleanup: + enabled: ${APP_FILESERVER_PLATFORM_CLEANUP_ENABLED:false} + interval: ${APP_FILESERVER_PLATFORM_CLEANUP_INTERVAL:60s} + max-items: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_ITEMS:100} + max-bytes: ${APP_FILESERVER_PLATFORM_CLEANUP_MAX_BYTES:1GB} + retry-backoff: ${APP_FILESERVER_PLATFORM_CLEANUP_RETRY_BACKOFF:5m} + tus: + enabled: ${APP_FILESERVER_PLATFORM_TUS_ENABLED:false} + httpbis-draft12: + # Unratified protocol; the contract can change without notice. + enabled: ${APP_FILESERVER_PLATFORM_HTTPBIS_DRAFT12_ENABLED:false} + nginx: + enabled: ${APP_FILESERVER_PLATFORM_NGINX_ENABLED:false} + internal-prefix: ${APP_FILESERVER_PLATFORM_NGINX_INTERNAL_PREFIX:/__files/} + object-suffix: ${APP_FILESERVER_PLATFORM_NGINX_OBJECT_SUFFIX:.bin} + minimum-size: ${APP_FILESERVER_PLATFORM_NGINX_MINIMUM_SIZE:16MB} + observability: + metrics-enabled: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_METRICS_ENABLED:true} + # Secret. Keyed HMAC over file identifiers; an unkeyed digest of an enumerable id is + # reversible, so startup fails while metrics are on and this is blank. + fingerprint-key: ${APP_FILESERVER_PLATFORM_OBSERVABILITY_FINGERPRINT_KEY:} diff --git a/src/app-bootstrap/src/main/resources/config/graphql.yml b/src/app-bootstrap/src/main/resources/config/graphql.yml new file mode 100644 index 00000000..a3a1d550 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/graphql.yml @@ -0,0 +1,12 @@ +# GraphQL 트랜스포트 스위치. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +backend: + graphql: + # true | false — publishes /graphql, including the route Spring GraphQL would publish itself. + enabled: ${APP_GRAPHQL_ENABLED:false} + # Required when the switch above is true, and must match the runtime environment + # (local->LOCAL, dev->DEV, prod->PRODUCTION_INTERNAL|PRODUCTION_PUBLIC). Replaces the + # production boolean and environment enum, which could disagree with each other. + deployment-mode: ${APP_GRAPHQL_DEPLOYMENT_MODE:} diff --git a/src/app-bootstrap/src/main/resources/config/httpclient.yml b/src/app-bootstrap/src/main/resources/config/httpclient.yml new file mode 100644 index 00000000..980c9386 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/httpclient.yml @@ -0,0 +1,7 @@ +# 아웃바운드 HTTP 클라이언트 플랫폼 스위치. 나머지 키는 환경변수에서 직접 바인딩된다. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +app: + httpclient: + enabled: ${APP_HTTPCLIENT_ENABLED:false} diff --git a/src/app-bootstrap/src/main/resources/config/idempotency.yml b/src/app-bootstrap/src/main/resources/config/idempotency.yml new file mode 100644 index 00000000..7d3209b5 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/idempotency.yml @@ -0,0 +1,10 @@ +# 멱등성 TTL 상한. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +ca-skeleton: + idempotency: + # feature-rate-limit-idempotency-contract D6/§E. ttl is env-driven (<=72h, + # validated in IdempotencyProperties); reaper-interval is literal operational tuning. + ttl: ${APP_IDEMPOTENCY_TTL:24h} + reaper-interval: 10m diff --git a/src/app-bootstrap/src/main/resources/config/messaging.yml b/src/app-bootstrap/src/main/resources/config/messaging.yml new file mode 100644 index 00000000..2d752237 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/messaging.yml @@ -0,0 +1,16 @@ +# 벤더드 메시징 플랫폼 스위치. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +app: + messaging: + # true | false — whether there is a broker at all. `broker` below selects which one; a + # blank broker used to be the de-facto switch, which is a selector doing a switch's job. + enabled: ${APP_MESSAGING_ENABLED:false} + # Active message broker id (e.g. kafka). Blank = messaging disabled (fail-fast on use). + # Selects the single MessageBroker; adding a broker is new files only (MessagingConfig). + broker: ${APP_MESSAGING_BROKER:} + kafka: + # CSV of host:port; required (non-empty) only when broker=kafka. Bound + + # validated by KafkaAdapterSettings (format) + KafkaAdapterConfig (required-when-active). + brokers: ${APP_MESSAGING_KAFKA_BROKERS:} diff --git a/src/app-bootstrap/src/main/resources/config/notification.yml b/src/app-bootstrap/src/main/resources/config/notification.yml new file mode 100644 index 00000000..33d46077 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/notification.yml @@ -0,0 +1,145 @@ +# 알림 전송 플랫폼과 R0 레거시 제공자 선택. 두 트리가 한 파일에 있는 것은 의도다 — 운영자가 '알림'을 찾을 때 한 곳만 보면 된다. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +ca-skeleton: + notification: + platform: + # The master switch. False binds nothing at all. + enabled: ${APP_NOTIFICATION_PLATFORM_ENABLED:false} + # SERVING | INGEST_ONLY. SERVING refuses to start without a working provider; INGEST_ONLY + # stores requests and does not dispatch, which is the shape a migration or a drain wants. + mode: ${APP_NOTIFICATION_PLATFORM_MODE:SERVING} + dispatch: + claim-batch-size: ${APP_NOTIFICATION_PLATFORM_CLAIM_BATCH_SIZE:50} + # The lease must outlast a provider call plus its timeout, or a live worker's delivery is + # claimed by a second one while the first is still waiting on the provider. + lease-duration: ${APP_NOTIFICATION_PLATFORM_LEASE_DURATION:2m} + poll-interval: ${APP_NOTIFICATION_PLATFORM_POLL_INTERVAL:1s} + max-global-concurrency: ${APP_NOTIFICATION_PLATFORM_MAX_CONCURRENCY:64} + max-additional-attempts: ${APP_NOTIFICATION_PLATFORM_MAX_ADDITIONAL_ATTEMPTS:4} + max-queue-age: ${APP_NOTIFICATION_PLATFORM_MAX_QUEUE_AGE:24h} + # An ambiguous attempt means the request reached the provider and the outcome could not be + # read. Falling back would risk sending the same notification twice, so it is off. + allow-ambiguous-fallback: ${APP_NOTIFICATION_PLATFORM_ALLOW_AMBIGUOUS_FALLBACK:false} + callbacks: + enabled: ${APP_NOTIFICATION_PLATFORM_CALLBACKS_ENABLED:false} + # Ceiling is 65508 = the ciphertext column minus the AES-GCM nonce and tag. A larger value + # is refused at binding rather than at the database, after the callback was acknowledged. + max-body-bytes: ${APP_NOTIFICATION_PLATFORM_CALLBACK_MAX_BODY_BYTES:65508} + replay-skew: ${APP_NOTIFICATION_PLATFORM_CALLBACK_REPLAY_SKEW:5m} + # Peers whose Forwarded/X-Forwarded-* headers may be believed when reconstructing the URL a + # provider signed. Empty by default, and that is the safe default rather than the convenient + # one: with no entry the resolver uses what the container observed, so no caller can choose + # the URL its own signature is checked against. A deployment behind a load balancer lists its + # proxies here. + trusted-proxies: ${APP_NOTIFICATION_PLATFORM_CALLBACK_TRUSTED_PROXIES:} + # Provider profiles, keyed by profile id. None by default: an empty map with mode=SERVING is + # refused at startup, which is the intended outcome for "enabled but not configured". + # + # A profile looks like this — copy it under a profile id of your own and supply the secrets + # through the credential profile, never inline: + # + # providers: + # ses-primary: + # type: SES # APNS | FCM | SES | SMTP | TWILIO | WEB_PUSH | WEBHOOK + # enabled: true + # primary-for-channel: true # exactly one primary per channel + # environment: PRODUCTION + # credential-profile: ses-prod # resolved through SecretMaterialProvider + # timeout: 10s + # max-concurrency: 16 + # rate-per-second: 50 + # One shipped profile, disabled, because a map cannot be written by an environment variable. + # + # `ca-skeleton` contains a hyphen and an environment variable cannot express one — Spring maps + # `_` to `.`, never to `-` — so every setting in this tree reaches a deployment through an + # APP_ placeholder named here. A provider map with caller-chosen keys has no placeholder to be + # named by, which is why the tree shipped as `providers: {}` and no deployment could add one. + # + # So the template ships the reference family as one profile, off. A deployment that wants a + # different id or a second family declares it in its own YAML; a deployment that wants the + # common case sets APP_NOTIFICATION_PLATFORM_SMTP_ENABLED=true and the relay address below. + # + # Disabled profiles are skipped by assembly before their type is resolved, so this costs a + # deployment that does not want it exactly nothing. + providers: + smtp: + type: SMTP + enabled: ${APP_NOTIFICATION_PLATFORM_SMTP_ENABLED:false} + # Exactly one primary per channel; a second primary for EMAIL fails assembly by name. + primary-for-channel: ${APP_NOTIFICATION_PLATFORM_SMTP_PRIMARY:true} + environment: ${APP_NOTIFICATION_PLATFORM_SMTP_ENVIRONMENT:local} + credential-profile: ${APP_NOTIFICATION_PLATFORM_SMTP_CREDENTIAL_PROFILE:default} + timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_TIMEOUT:10s} + max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_MAX_CONCURRENCY:4} + rate-per-second: ${APP_NOTIFICATION_PLATFORM_SMTP_RATE_PER_SECOND:10} + # What the relay is, as opposed to which profile uses it. Host, port and credentials stay + # spring.mail.*; these are the parts Spring's mail properties have no word for. + smtp: + # STARTTLS_REQUIRED | IMPLICIT_TLS. There is no plaintext member: the type refuses an + # unencrypted relay by construction rather than by a validator somebody has to run. + tls-mode: ${APP_NOTIFICATION_PLATFORM_SMTP_TLS_MODE:STARTTLS_REQUIRED} + # example.invalid is reserved by RFC 2606 and resolves nowhere, so a deployment that forgot + # to set one gets a traceable bounce rather than mail apparently from an address it does not + # own. + sender-identity: ${APP_NOTIFICATION_PLATFORM_SMTP_SENDER_IDENTITY:no-reply@example.invalid} + connect-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_CONNECT_TIMEOUT:5s} + read-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_READ_TIMEOUT:10s} + write-timeout: ${APP_NOTIFICATION_PLATFORM_SMTP_WRITE_TIMEOUT:10s} + max-concurrency: ${APP_NOTIFICATION_PLATFORM_SMTP_DISPATCH_CONCURRENCY:4} + # ---- Key material ------------------------------------------------------- + # Eight purposes, eight keys, each base64 of at least 32 bytes and each distinct from every + # other. The platform decodes them at startup and refuses to boot if one is blank, short or + # shared: a blank value used to be skipped, so the platform started without the key and found + # out on the first contact point — in production, on a user's notification. + # + # These were bound by nothing until now. The subtree was missing from this file entirely while + # the settings record and its validator existed, so `enabled: true` was an unconditional boot + # failure in every environment and the only way to discover the eight names was to read the + # record. That is the gap this block closes; the empty defaults keep a deployment with the + # platform off from having to supply anything. + # + # Never write a value here. Supply them out of band, per environment. + secrets: + contact-encryption-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY:} + contact-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY:} + callback-signing-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY:} + provider-credential-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY:} + payload-encryption-key: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY:} + vapid-signing-key: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY:} + provider-request-lookup-hmac-key: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY:} + callback-fingerprint-hmac-key: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY:} + # The id written into every envelope this key produces, one per purpose, keyed by the + # purpose's own name. Not secret — an id identifies key material without revealing it — but + # required all the same, and with no default on purpose: a constant id makes a rotation + # indistinguishable from the key it replaced, so nothing could decrypt what was written + # before it. Change the id in the same deployment that changes the material. + active-key-ids: + CONTACT_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_CONTACT_ENCRYPTION_KEY_ID:} + CONTACT_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_CONTACT_LOOKUP_HMAC_KEY_ID:} + CALLBACK_SIGNING: ${APP_NOTIFICATION_PLATFORM_CALLBACK_SIGNING_KEY_ID:} + PROVIDER_CREDENTIAL: ${APP_NOTIFICATION_PLATFORM_PROVIDER_CREDENTIAL_KEY_ID:} + PAYLOAD_ENCRYPTION: ${APP_NOTIFICATION_PLATFORM_PAYLOAD_ENCRYPTION_KEY_ID:} + VAPID_SIGNING: ${APP_NOTIFICATION_PLATFORM_VAPID_SIGNING_KEY_ID:} + PROVIDER_REQUEST_LOOKUP_HMAC: ${APP_NOTIFICATION_PLATFORM_PROVIDER_REQUEST_LOOKUP_HMAC_KEY_ID:} + CALLBACK_FINGERPRINT_HMAC: ${APP_NOTIFICATION_PLATFORM_CALLBACK_FINGERPRINT_HMAC_KEY_ID:} + # Superseded material, kept readable, grouped by the purpose it served and then by the id + # it was active under — historical-keys.PAYLOAD_ENCRYPTION.payload-2026-01. The purpose is + # not recoverable from an id, and every consumer that resolves a key by id refuses one + # carrying the wrong purpose, so a retired key filed under the wrong heading resolves and + # is then rejected. Unset until a first rotation; a deployment that has never rotated needs + # none. See docs/notification/at-rest-threat-model.md for the rotation sequence. + historical-keys: {} + +app: + notification: + # Active provider id per kind; blank = that kind disabled (fail-fast on use). + # Add a provider = new files only (NotificationConfig); select it here. + # This is the R0 legacy seam. The delivery platform is the separate + # ca-skeleton.notification.platform tree below. + slack: + provider: ${APP_NOTIFICATION_SLACK_PROVIDER:} + email: + provider: ${APP_NOTIFICATION_EMAIL_PROVIDER:} + diff --git a/src/app-bootstrap/src/main/resources/config/observability.yml b/src/app-bootstrap/src/main/resources/config/observability.yml new file mode 100644 index 00000000..7c500b4a --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/observability.yml @@ -0,0 +1,175 @@ +# 액추에이터 노출면, OpenAPI, 로그 레벨, 로깅/프라이버시/트레이싱 설정. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +# --------------------------------------------------------------------------- +# Actuator / management — MERGED ownership at integration: +# - feature-management-actuator-security-contract owns server.port, the exposure +# allowlist/exclude, health show-details, shutdown-disabled, and info. +# - feature-runtime-health-lifecycle-contract owns the health probe SHAPE +# (probes.enabled + the liveness/readiness/startup groups + dependency taxonomy). +# --------------------------------------------------------------------------- +# --------------------------------------------------------------------------- +# OpenAPI. Stated rather than defaulted: SpringDoc logs a startup WARN telling every deployment to +# decide this, and a warning that appears on every start is one nobody reads. The template decides — +# on where it is useful, off under prod, which application-prod.yml pins so the choice cannot be +# widened by an environment variable in the one runtime where publishing the whole API shape is a +# reconnaissance surface. +# --------------------------------------------------------------------------- +springdoc: + api-docs: + enabled: ${APP_OPENAPI_DOCS_ENABLED:true} + swagger-ui: + enabled: ${APP_OPENAPI_UI_ENABLED:true} + +management: + # feature-management-actuator-security-contract D1: separate management port so actuator + # endpoints are not exposed on the same socket as the app API. Default: 9001. + server: + port: ${MANAGEMENT_SERVER_PORT:9001} + endpoints: + web: + # D2: production allowlist — only safe, scrape-friendly endpoints are exposed. + exposure: + # adapteractivation reports which master switches resolved on and what that makes required. + # It reads the environment and creates nothing, so it leaks no secret and opens no resource — + # and it is exposed precisely so a smoke lane can check the application's own answer rather + # than the flags the lane itself passed in, which is a test of the test. + include: health,prometheus,info,loggers,adapteractivation + # D2/D4/D5: explicitly excluded dangerous endpoints (env leaks secrets, + # heapdump/threaddump = memory forensics, shutdown = remote kill, configprops = secret leak). + exclude: env,configprops,heapdump,threaddump,shutdown + endpoint: + health: + # D8: never expose health details to unauthenticated callers. + show-details: when-authorized + # Membership is validated. With validation off, a group naming a contributor that does not + # exist is silently dropped and readiness reports UP while proving nothing about the + # dependency it claims to gate on — a false green that survives exactly as long as nobody + # checks. On, the cost is that every name below must resolve, in every deployment. + # + # That cost is paid by construction rather than by convention: the groups below name only + # unconditional contributors, and the one conditional member — `redisRequired` — is appended + # by RedisReadinessGroupPostProcessor from the same predicate that creates the bean. A + # contributor can therefore be named only where it exists, and a misspelled one still fails + # startup. + validate-group-membership: true + # feature-runtime-health-lifecycle-contract: expose the Kubernetes-ready + # liveness/readiness/startup probe paths. + probes: + enabled: true + group: + # Liveness: JVM can continue (OOM → liveness DOWN → pod restart). + # Only livenessState is included; dependency health MUST NOT be here — + # a DOWN DB must never trigger a pod restart when the JVM is healthy. + liveness: + include: livenessState + # Readiness: ready to serve traffic AND all REQUIRED dependencies up. + # Every name here must resolve to a contributor that exists (validate-group-membership + # above). A correctness-role Redis contributor belongs in this list and is added when the + # Redis runtime composition creates one; naming it before then produced a readiness probe + # that reported UP without ever checking Redis. + # `redisRequired` is NOT listed here, and must not be: group membership does not tolerate a + # conditional contributor being absent. Boot validates the group against the contributors + # that exist, so naming it statically made every Redis-off and cache-only deployment fail at + # startup with "Included health contributor 'redisRequired' in group 'readiness' does not + # exist". RedisReadinessGroupPostProcessor appends it to this list — from the same predicate + # that creates the bean — when Redis is on and a correctness role selected it. + # `redisOptional` is never appended: a cache outage is reported as degraded detail and never + # turns a healthy JVM or an otherwise-ready pod unavailable. + readiness: + include: readinessState + # Startup: startup/migration validation complete. + # readinessState acts as the startup completion gate — it flips UP only + # after the context is fully initialized (Flyway migration included). + startup: + include: readinessState + shutdown: + # D4: shutdown endpoint disabled globally — even if somehow exposed, it cannot be invoked. + access: none + info: + build: + # Build-info only (no env leak). + enabled: true + +logging: + level: + # TRACE | DEBUG | INFO | WARN | ERROR | OFF + # + # Inline defaults, because these had none and only application-local.yml pinned them. That made + # local the one environment that could start without an operator supplying every level: dev and + # prod failed to bind `logging.level.root` before reaching any of their own settings, which is a + # log-configuration problem wearing a startup failure's clothes. + root: ${APP_LOG_LEVEL_ROOT:INFO} + dev.caskeleton: ${APP_LOG_LEVEL_APP:INFO} + org.springframework: ${APP_LOG_LEVEL_SPRING:INFO} + org.springframework.web: ${APP_LOG_LEVEL_WEB:INFO} + # DEBUG here prints SQL once JPA/jdbc is wired in + org.hibernate.SQL: ${APP_LOG_LEVEL_SQL:INFO} + +# Module-scoped knobs. Each block is bound into a *Settings @ConfigurationProperties +# record in the corresponding module, which is where allowed-value validation lives. +ca-skeleton: + logging: + file: + # true | false (wraps console + adds rolling JSON file appender) + enabled: ${APP_LOG_FILE_ENABLED:false} + # relative (to src/) or absolute + path: ${APP_LOG_FILE_PATH:logs/ca-skeleton.json} + # size with unit: KB | MB | GB + max-size: ${APP_LOG_FILE_MAX_SIZE:100MB} + # integer >= 1 + max-history: ${APP_LOG_FILE_MAX_HISTORY:14} + # size with unit or 0 + total-size-cap: ${APP_LOG_FILE_TOTAL_SIZE_CAP:3GB} + async: + # true | false (AsyncAppender wrapper for non-blocking I/O) + enabled: ${APP_LOG_ASYNC_ENABLED:true} + # integer >= 1 + queue-size: ${APP_LOG_ASYNC_QUEUE_SIZE:512} + # integer >= 0 (0 = never drop) + discarding-threshold: ${APP_LOG_ASYNC_DISCARDING_THRESHOLD:20} + json: + # IANA timezone (UTC | Asia/Seoul | ...) or "default" + timezone: ${APP_LOG_JSON_TIMEZONE:UTC} + # ISO 8601 pattern + timestamp-pattern: ${APP_LOG_JSON_TIMESTAMP_PATTERN:yyyy-MM-dd'T'HH:mm:ss.SSSXXX} + # true | false (file/method/line — expensive) + include-caller-data: ${APP_LOG_JSON_INCLUDE_CALLER_DATA:false} + # integer; 0 = full name, positive = abbreviated + logger-name-length: ${APP_LOG_JSON_LOGGER_NAME_LENGTH:0} + # float in [0.0, 1.0] — keep-probability for <=INFO logs (prod 0.1 = 10% sampling; + # WARN/ERROR always 100%). Consumed by SamplingTurboFilter via logback . + sampling-rate: ${APP_LOG_SAMPLING_RATE:1.0} + privacy: + # secret-tier HMAC salt for user_principal pseudonymization (DRIFT-6; algorithm SSOT + # feature-data-retention-privacy-contract). Blank -> PrivacySettings warns + uses a dev + # sentinel; prod MUST supply a real secret-manager value. + pseudonymization-salt: ${APP_PRIVACY_PSEUDONYMIZATION_SALT:} + # feature-distributed-tracing-contract D1/D4/D6 — tracing seam settings. + # The OTel/Micrometer tracer runtime is active in this repo (Plan A — seam activated): + # micrometer-tracing-bridge-otel + opentelemetry-exporter-otlp are wired in + # app-bootstrap/build.gradle. The exporter remains off while OTEL_EXPORTER_OTLP_ENDPOINT + # is blank (url_or_empty default). A fork can supply the endpoint via env or override + # SpanErrorRecorder with its own bean. + # D4 disabled-fallback: even when enabled=false, RequestLoggingFilter always + # generates a W3C traceparent so meta.traceId and log trace_id are never null. + # D6 per-profile defaults: prod=0.01 / staging=0.10 / dev·local=1.0. + # APP_TRACING_SAMPLE_RATE overrides the per-profile default when set. + tracing: + # true | false (boolean_strict). Tracing seam on/off. + enabled: ${APP_TRACING_ENABLED:true} + # float in [0.0, 1.0]. Per-profile override (D6 float_between_0_and_1 validation at startup). + # blank = per-profile default via TracingSampleRateResolver (D-1 ISSUE-1 fix). + sample-rate: ${APP_TRACING_SAMPLE_RATE:} + exporter: + # url_or_empty: blank = exporter off (D1 SEAM default); non-blank must be a valid URL. + otlp-endpoint: ${OTEL_EXPORTER_OTLP_ENDPOINT:} + +# feature-integration-adapter-templates — optional integration adapter toggles. +# The `app...enabled` flags feed Spring @ConditionalOnProperty (Layer 1): +# disabled (default): messaging binds a fail-fast Disabled* sentinel bean; cache/notification +# contribute nothing and fail fast in their router (CacheStoreRouter / RoutingNotifier) on +# unbound access. Enabled registers the real adapter +# (which needs its project-supplied integration client bean). Domain namespace, NOT a +# generic `app.adapter.*` prefix (branch-note §Audit A1). Env keys are the registry SSOT. diff --git a/src/app-bootstrap/src/main/resources/config/outbox.yml b/src/app-bootstrap/src/main/resources/config/outbox.yml new file mode 100644 index 00000000..381b8628 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/outbox.yml @@ -0,0 +1,30 @@ +# 트랜잭셔널 아웃박스 릴레이. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +ca-skeleton: + outbox: + # feature-domain-event-outbox-contract I11 — all six values are literal defaults; + # NO env placeholders (spec: 신규 env key 없음). Bound into OutboxProperties. + # true | false — enable/disable the relay scheduler (OutboxRelayScheduler). + # MSG-024: an enabled relay with a blank app.messaging.broker fails startup + # (OutboxRelayBrokerRequirementValidator). The relay would otherwise claim PENDING rows, fail + # every publish against the disabled publisher, and exhaust them to DEAD — a messaging-off + # environment must preserve its outbox, not destroy it one retry budget at a time. + # true | false — the capability switch. Off means no outbox settings, validator, leader token, + # metrics, or store binding at all; relay-enabled below only decides whether the scheduler runs. + # Conflating the two meant a relay-off deployment still assembled an OutboxMetrics over a store + # port that a database-less deployment does not have. + enabled: ${APP_OUTBOX_ENABLED:false} + relay-enabled: ${APP_OUTBOX_RELAY_ENABLED:false} + # ISO-8601 duration — how often the relay polls for pending rows + poll-interval: PT5S + # integer >= 1 — maximum rows claimed per relay cycle + batch-size: 20 + # ISO-8601 duration — IN_FLIGHT orphan visibility window (I6: next_attempt_at reuse) + in-flight-timeout: PT5M + # ISO-8601 duration — read also by adapter-persistence OutboxReaper @Scheduled + reaper-interval: PT10M + # ISO-8601 duration — PUBLISHED row retention before reaper deletes them (I3) + # read also by adapter-persistence OutboxReaper via ${ca-skeleton.outbox.published-retention:P7D} + published-retention: P7D diff --git a/src/app-bootstrap/src/main/resources/config/persistence.yml b/src/app-bootstrap/src/main/resources/config/persistence.yml new file mode 100644 index 00000000..eef4eedd --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/persistence.yml @@ -0,0 +1,41 @@ +# 관계형/문서 저장소 활성화와 JPA·persistence 정책. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +ca-skeleton: + # The five optional adapters ship in this artifact and are off unless a deployment says otherwise, + # so one jar serves every environment and an operator turns a capability on with one variable. Off + # is structural: no settings bind, no bean is created, no socket, pool or thread exists, and a + # malformed detail setting for a capability nobody enabled does not block startup. + # Registry SSOT: docs/registries/env-keys.yaml. + persistence-jpa: + # true | false — relational persistence: DataSource, entity scan, repositories, Flyway. + enabled: ${APP_PERSISTENCE_JPA_ENABLED:false} + persistence-mongo: + # true | false + enabled: ${APP_PERSISTENCE_MONGO_ENABLED:false} + # Required non-blank when the switch above is true. Exactly one profile: the runtime builds one + # sync client and one pool, and never resolves a non-selected profile's secret. + active-profile: ${APP_PERSISTENCE_MONGO_ACTIVE_PROFILE:} + # Canonical HTTP capability activation. Bindings are the sole activation SSOT: provider + # definitions alone are inert, and the current NOT_IMPLEMENTED readiness card rejects ACTIVE + # before any client/executor/pool resource can be created. + persistence: + vendor: postgresql + # JPA named-policy deadline envelope. JpaTransactionSettings validates the hierarchy; + # SpringPolicyTransactionPort intersects these limits with the caller's absolute CallBudget + # and the actual Hikari connection timeout before acquiring a transaction. + jpa: + transaction: + transaction-timeout: 30s + begin-budget: 250ms + minimum-action-window: 1s + completion-margin: 500ms + statement-timeout: 10s + lock-timeout: 2s + idle-guard-timeout: 15s + transaction-margin: 250ms + lock-margin: 100ms + retry-base-delay: 10ms + retry-maximum-delay: 50ms + retry-maximum-attempts: 2 diff --git a/src/app-bootstrap/src/main/resources/config/ratelimit.yml b/src/app-bootstrap/src/main/resources/config/ratelimit.yml new file mode 100644 index 00000000..04b3efa4 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/ratelimit.yml @@ -0,0 +1,17 @@ +# 엣지 레이트 리밋 정책. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +app: + rate-limit: + # Inbound HTTP enforcement is a separate axis from outbound provider activation. + # enabled=true with no exact EdgeRateLimitPort fails fast; it never installs a local fallback. + enabled: ${APP_RATE_LIMIT_ENABLED:false} + default-policy-id: ${APP_RATE_LIMIT_DEFAULT_POLICY_ID:api-default} + hash-key-version: ${APP_RATE_LIMIT_HASH_KEY_VERSION:1} + caller-deadline-budget: 2s + # remote-addr-only | forwarded-headers-trusted (trusted ingress only) + client-ip-mode: ${APP_RATE_LIMIT_CLIENT_IP_MODE:remote-addr-only} + # Logical-cache-name → backendId routing (CacheStoreRouter). No keys by default — + # forks add e.g. `bindings: { worklog: redis }` or env APP_CACHE_BINDINGS_WORKLOG=redis. + # A binding to a backend that is not enabled fails startup (Layer 3 moved to router). diff --git a/src/app-bootstrap/src/main/resources/config/redis.yml b/src/app-bootstrap/src/main/resources/config/redis.yml new file mode 100644 index 00000000..31092396 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/redis.yml @@ -0,0 +1,15 @@ +# Redis 전역 스위치와 SDK 네임스페이스. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +app: + # The one global Redis switch. False (the default) means no Redis settings are bound, no Redis + # credential is required, no client, connection, thread or health contributor is created, and no + # Redis-specific resource is read. There is deliberately no second master switch: a role such as + # cache or session selects *which* Redis capabilities compose, never *whether* Redis exists. + redis: + enabled: ${APP_REDIS_ENABLED:false} + # Fileserver R2 exact destination/provider composition. Disabled by default: while false, + # these blank attestation placeholders do not create directories, probe a filesystem, or + # contribute FilePublicationPort. Enabling fails closed unless every local-persistent + # attestation value matches the pre-provisioned root. No implicit local fallback exists. diff --git a/src/app-bootstrap/src/main/resources/config/runtime.yml b/src/app-bootstrap/src/main/resources/config/runtime.yml new file mode 100644 index 00000000..974ce6c9 --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/runtime.yml @@ -0,0 +1,65 @@ +# 부트스트랩 신원, 런타임 안전 토글, 분산 락, 표현 계층, 비동기 실행기. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +ca-skeleton: + bootstrap: + # required, non-blank — startup fails if blank (see BootstrapSettings) + app-name: ${APP_NAME} + runtime: + # feature-env-driven-runtime-configuration D8: enforced at startup by + # StartupSafetyValidator. The two prod-unsafe toggles fail startup if true under + # the prod profile; multi-instance fails startup if its coordination beans are absent. + # true | false (internal error detail in responses — forbidden under prod) + error-detail-exposure-enabled: ${APP_ERROR_DETAIL_EXPOSURE_ENABLED:false} + # true | false (request/response body capture in logs — forbidden under prod) + log-body-capture-enabled: ${APP_LOG_BODY_CAPTURE_ENABLED:false} + # true | false (requires the 5 instance-coordination beans when true) + multi-instance-enabled: ${APP_MULTI_INSTANCE_ENABLED:false} + # true | false (whether to run Flyway migrations automatically at startup) + migration-on-startup: ${APP_MIGRATION_ON_STARTUP:true} + # feature-distributed-lock-contract D5 — distributed lock acquisition contract (try-lock + + # finite wait + lease TTL). Bound to LockSettings (adapter-persistence). Plain values, NOT + # APP_* env keys — new env-key registration is feature-env-driven-runtime-configuration's + # domain (out of scope for this branch); code defaults in LockSettings mirror these. + lock: + wait-time: 3s + lease-ttl: 30s + # Which RDBMS vendor composition the persistence adapter registers: postgresql | h2. Bound to + # PersistenceVendorSettings (adapter-persistence), which rejects any other value at startup. + # + # A property rather than a profile check, because the vendor belongs to the datastore and not to + # the environment that happens to use it. The profiles then state their choice: + # application-local.yml selects h2, application-dev.yml and application-prod.yml select + # postgresql, and prod additionally refuses h2 through PersistenceVendorProdSafetyValidator. + # + # The value here is the default for a deployment that activates no profile at all — PostgreSQL, + # the vendor every deployment ran before this selector existed. + # Notification delivery platform. Disabled by default, and disabled means nothing is bound: no + # provider runtime, no schema activation check, no scheduler thread, no secret required. Enabling + # it needs a real database with the notification schema stream applied and at least one provider + # profile with credentials — see docs/notification/configuration-reference.md, which is the + # reference this tree is kept in step with. + # + # Every value carries an inline default so that a deployment leaving the platform off never has to + # supply an environment variable for a subsystem it is not running. The tree existed only as + # relaxed-binding environment variables until 2026-08-15: a template user could not discover which + # profile, secret, callback and activation settings had to line up, because nothing wrote them + # down together. + presentation: + # feature-api-contract-baseline D2: API version prefix. Default is the URI + # prefix "/v1" (major-version path, AIP-185); override via env, or set "" for + # no prefix. The supplemental "X-Api-Version" header never overrides the path. + api-base-path: ${PRESENTATION_API_BASE_PATH:/v1} + async: + # feature-background-job-async-contract D7 — @Async ThreadPoolTaskExecutor pool sizing. + # Registry SSOT: docs/registries/env-keys.yaml (APP_ASYNC_EXECUTOR_* rows 1337-1377). + # Bound into AsyncExecutorSettings @ConfigurationProperties(prefix = "ca-skeleton.async.executor"). + # Bounded queue is mandatory (D7) — an unbounded queue makes max-size unreachable. + executor: + # int >= 1 (positive_int). Always-alive worker count. + core-size: ${APP_ASYNC_EXECUTOR_CORE_SIZE:10} + # int >= core-size (positive_int_ge_core). Hard ceiling on workers. + max-size: ${APP_ASYNC_EXECUTOR_MAX_SIZE:50} + # int in 1..) + # + # Derived from the API base path, not restated. The default here was the literal + # /api/healthcheck while presentation.api-base-path defaults to /v1, so the two shipped defaults + # described one address and disagreed about it: the health endpoint was published at + # /v1/healthcheck and the allowlist opened /api/healthcheck, a path no handler serves. The + # public health endpoint was therefore not public in any runtime except local, which pins both + # to /api and so never saw it. Found by the shared-infra-dev lane, where the smoke client got a + # refusal from an application that had started perfectly. + public-paths: ${SECURITY_PUBLIC_PATHS:${PRESENTATION_API_BASE_PATH:/v1}/healthcheck} + session: + cookie-name: ${APP_SESSION_COOKIE_NAME:CA_SESSION} + secure: ${APP_SESSION_COOKIE_SECURE:true} + http-only: ${APP_SESSION_COOKIE_HTTP_ONLY:true} + same-site: ${APP_SESSION_COOKIE_SAME_SITE:Lax} + path: ${APP_SESSION_COOKIE_PATH:/} + csrf-cookie-name: ${APP_SESSION_CSRF_COOKIE_NAME:XSRF-TOKEN} + csrf-header-name: ${APP_SESSION_CSRF_HEADER_NAME:X-XSRF-TOKEN} + authz: + # feature-authentication-authorization-contract D2/D3/D8: app-side role→permission + # mapping (the default source; IdP-issued permission claims are an out-of-scope + # alternative). Keys are RAW IdP role names (no ROLE_ prefix — that prefix only + # exists on Spring authorities, not on the principal's raw role set), looked up + # case-insensitively. Values are explicitly enumerated `resource:action` permissions + # (no wildcards — least-privilege, OWASP-AUTHZ-C4). The values below are the + # sample-portfolio demonstration (§5): `user` may read/write, only `admin` may close. + role-permissions: + user: worklog:read,worklog:write + admin: worklog:read,worklog:write,worklog:close + # The notification platform's own two permissions, granted to two roles rather than one. + # Publishing a template changes what every future submission renders, so a service that may + # send notifications is not thereby a service that may rewrite them; a deployment that wants + # both grants both roles. + notification-submit: notification:submit + notification-template-publish: notification-template:publish + cors: + # true | false + enabled: ${APP_SECURITY_CORS_ENABLED:false} + # comma-separated. Empty by default, unlike the other undefaulted values: CORS is off by + # default, and an empty origin list is the safest possible value rather than an arbitrary one. + # The rule those others follow — no default for an address, a credential, or an identity — is + # about not running against something nobody chose; allowing nobody is a choice. + allowed-origins: ${APP_SECURITY_CORS_ORIGINS:} + # comma-separated; empty -> defaults + allowed-methods: ${APP_SECURITY_CORS_ALLOWED_METHODS:} + # comma-separated; "*" allows any + allowed-headers: ${APP_SECURITY_CORS_ALLOWED_HEADERS:*} + # true | false + allow-credentials: ${APP_SECURITY_CORS_ALLOW_CREDENTIALS:false} + # seconds + max-age-seconds: ${APP_SECURITY_CORS_MAX_AGE:600} diff --git a/src/app-bootstrap/src/main/resources/config/server.yml b/src/app-bootstrap/src/main/resources/config/server.yml new file mode 100644 index 00000000..16014fdf --- /dev/null +++ b/src/app-bootstrap/src/main/resources/config/server.yml @@ -0,0 +1,28 @@ +# 서버 포트·종료·프록시 헤더·압축. +# +# application.yml 이 spring.config.import 로 취합한다. 이 파일의 값은 application.yml 보다 +# 낮고 application-{profile}.yml 보다도 낮으므로, 배포별 오버라이드는 그대로 우선한다. +server: + # integer 1-65535 + port: ${APP_SERVER_PORT:8080} + # graceful | immediate + shutdown: ${APP_SERVER_SHUTDOWN:graceful} + # none | native | framework (trust X-Forwarded-* behind LB/proxy) + forward-headers-strategy: ${APP_SERVER_FORWARD_HEADERS_STRATEGY:framework} + tomcat: + threads: + # integer >= 1 + max: ${APP_SERVER_TOMCAT_MAX_THREADS:200} + # integer >= 0 + min-spare: ${APP_SERVER_TOMCAT_MIN_SPARE_THREADS:10} + # integer >= 0 (OS-level backlog queue depth) + accept-count: ${APP_SERVER_TOMCAT_ACCEPT_COUNT:100} + # integer >= 1 (hard cap on simultaneously open connections) + max-connections: ${APP_SERVER_TOMCAT_MAX_CONNECTIONS:8192} + # duration: 20s | 1m + connection-timeout: ${APP_SERVER_TOMCAT_CONNECTION_TIMEOUT:20s} + compression: + # true | false + enabled: ${APP_SERVER_COMPRESSION_ENABLED:true} + # bytes or sized: 1024 | 1KB | 2KB + min-response-size: ${APP_SERVER_COMPRESSION_MIN_RESPONSE_SIZE:1KB} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java index 55a50234..8304834a 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/AdapterActivationInventory.java @@ -138,7 +138,7 @@ public final class AdapterActivationInventory { new Ownership( Set.of( "dev.caskeleton.adapter.outbound.notification", - "dev.caskeleton.bootstrap.notification"), + "dev.caskeleton.bootstrap.autoconfigure.notification"), Set.of(), "notification-")); byAdapter.put( diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java index 0a65e2bd..ec4d36c3 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/MasterSwitchRegistryContractTest.java @@ -2,6 +2,7 @@ package dev.caskeleton.bootstrap.activation; import static org.assertj.core.api.Assertions.assertThat; +import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration; import dev.caskeleton.shared.activation.MasterSwitch; import java.io.IOException; import java.nio.file.Files; @@ -27,7 +28,9 @@ class MasterSwitchRegistryContractTest { @DisplayName("every switch is registered, defaults to false, and is bound by the shipped YAML") void everySwitchAgreesAcrossItsThreeHomes(MasterSwitch masterSwitch) throws IOException { String registry = read("docs/registries/env-keys.yaml"); - String applicationYaml = read("src/app-bootstrap/src/main/resources/application.yml"); + // Every shipped resource, not application.yml alone: the capability blocks these + // switches bind moved to config/*.yml when the 974-line file was split (BOOT-008). + String applicationYaml = ShippedConfiguration.text(); assertThat(registry) .as( diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java index 2f8e6c7b..11a2279c 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/activation/NotificationRootImportCoverageTest.java @@ -2,7 +2,7 @@ package dev.caskeleton.bootstrap.activation; import static org.assertj.core.api.Assertions.assertThat; -import dev.caskeleton.bootstrap.notification.NotificationRootAutoConfiguration; +import dev.caskeleton.bootstrap.autoconfigure.notification.NotificationRootAutoConfiguration; import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -38,17 +38,17 @@ class NotificationRootImportCoverageTest { + "scan narrowed, a root that does not name them leaves the capability broken when " + "it is switched on, and quieter when it is off — which reads as success") .contains( - "dev.caskeleton.bootstrap.notification.NotificationPlatformRuntimeConfig", - "dev.caskeleton.bootstrap.notification.NotificationPlatformSecretsConfig", - "dev.caskeleton.bootstrap.notification.NotificationPlatformRegistriesConfig"); + "dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformRuntimeConfig", + "dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformSecretsConfig", + "dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformRegistriesConfig"); } @Test @DisplayName("the delivery-platform runtime still names its own children") void theRuntimeConfigNamesItsChildren() { Import imports = - dev.caskeleton.bootstrap.notification.NotificationPlatformRuntimeConfig.class.getAnnotation( - Import.class); + dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformRuntimeConfig.class + .getAnnotation(Import.class); assertThat(imports).isNotNull(); assertThat(Arrays.stream(imports.value()).map(Class::getSimpleName).toList()) diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java new file mode 100644 index 00000000..7949b51e --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/AdapterBoundaryArchitectureTest.java @@ -0,0 +1,620 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; +import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import java.util.Map; +import java.util.Set; + +/** + * The boundaries between adapters: inbound, outbound and persistence stay unaware of each other, + * and the composition root reaches each one only through its exported surface. Adapters are peers, + * not a layer — an edge between two of them is a dependency nobody declared. + * + *

어댑터끼리의 경계 — 인바운드/아웃바운드/영속성이 서로를 모르게 유지한다. + * + *

{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by + * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields + * inside a nested class — the rules would have stopped running while the build stayed green + * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share + * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest} + * holds the full rule inventory so none can be lost in a move like this one. + */ +@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class) +class AdapterBoundaryArchitectureTest { + + /** + * A transport must depend on an inbound port, never on the class behind it. + * + *

The notification callback controller injected {@code + * IngestProviderCallbackApplicationUseCase} directly. The existing capability rules did not catch + * it, and could not: they inspect types that already implement {@code CommandUseCase} or {@code + * QueryUseCase}, so an orchestration that never adopted the marker is invisible to the very gate + * that would have required it. This rule looks from the other side — at what the transport + * depends on — which is where the omission is visible. + */ + @ArchTest + static final ArchRule INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.adapter.inbound..") + .should() + .dependOnClassesThat( + new com.tngtech.archunit.base.DescribedPredicate<>( + "are concrete application orchestration services") { + @Override + public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { + return candidate.getPackageName().startsWith("dev.caskeleton.application.") + && !candidate.isInterface() + && !candidate + .getModifiers() + .contains(com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT) + && (candidate.getSimpleName().endsWith("Service") + || candidate.getSimpleName().endsWith("Orchestrator")); + } + }) + .because( + "INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES: an entrypoint reached " + + "through a concrete class bypasses the use-case fitness gate, which is what " + + "declares transaction mode, repository access and outbound permission") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule WEB_ADAPTER_DOES_NOT_DEPEND_ON_PERSISTENCE_OR_OUTBOUND_ADAPTERS = + noClasses() + .that() + .resideInAPackage("..adapter.inbound.web..") + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "..adapter.outbound.persistence..", + "..adapter.outbound..", + "..adapter.outbound.identifier..", + "org.springframework.data.repository..", + "org.springframework.data.jpa.repository..", + "jakarta.persistence..", + "javax.persistence..", + "org.hibernate.."); + + @ArchTest + static final ArchRule PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS = + noClasses() + .that() + .resideInAPackage("..adapter.outbound.persistence..") + .should() + // persistence now nests under adapter.outbound (topology rename), so the forbidden + // outbound target must exclude persistence's own package — otherwise the abundant + // intra-persistence references (adapter -> entity/mapper/repository) self-overlap. + // Mirrors the IDENTIFIER_ADAPTER_... predicate-form self-exclusion below. + .dependOnClassesThat( + JavaClass.Predicates.resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..") + .or( + JavaClass.Predicates.resideInAPackage("..adapter.outbound..") + .and( + JavaClass.Predicates.resideOutsideOfPackage( + "..adapter.outbound.persistence..")))) + .as( + "persistence-jpa is an internal (driven) outbound adapter — it must not reach into " + + "the web adapter, sibling outbound adapters, or the composition root " + + "(intra-persistence references are allowed).") + .allowEmptyShould(true); + + // ---- persistence-rdbms/no-sql-extensibility (RDBMS base vendor-neutrality) ---- + // adapter-persistence-rdbms (all of ..adapter.outbound.persistence.. EXCEPT + // ..adapter.outbound.persistence.postgresql..) + // must not import PostgreSQL driver or flyway-database-postgresql types. Those vendor-specific + // dependencies belong only in adapter-persistence-postgresql. + // + // Future NoSQL adapter modules (for example adapter-persistence-mongodb) must be added as + // sibling modules that implement application/domain ports directly. When such a module exists, + // add an ArchUnit rule forbidding jakarta.persistence.., org.hibernate.., + // org.springframework.data.jpa.., org.flywaydb.., and + // dev.caskeleton.adapter.outbound.persistence.postgresql.. + // dependencies from that module. + + /** + * The composition root reaches only the persistence leaf's exported surface. + * + *

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

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

{@code JpaNotificationRequestStore} imported {@code NotificationSubmissionService} and + * called its roll-up to work out what a request's status should be — so "one recipient completed + * and one failed means partially completed", a product rule, lived behind a JPA store, and + * changing it meant editing the application and the database layer in step. + * + *

The test is concreteness, not naming. An application interface named {@code ...Service} — + * {@code FileQuotaService}, for instance — is a port: the adapter implements it, so the + * dependency points from infrastructure to a contract, which is the direction this architecture + * wants. A concrete application service is orchestration, and an adapter that calls one has taken + * over a decision the use case owns. + */ + @ArchTest + static final ArchRule PERSISTENCE_DOES_NOT_DEPEND_ON_APPLICATION_SERVICES = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") + .should() + .dependOnClassesThat( + new com.tngtech.archunit.base.DescribedPredicate<>( + "application services or use cases") { + @Override + public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { + String name = candidate.getName(); + return name.startsWith("dev.caskeleton.application.") + && !candidate.isInterface() + && (candidate.getSimpleName().endsWith("Service") + || candidate.getSimpleName().endsWith("UseCase")); + } + }) + .as( + "adapter:outbound:persistence-jpa must not depend on application services or use " + + "cases: an adapter that calls one has taken over a decision the use case owns.") + .allowEmptyShould(true); + + static final ArchRule PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") + .and() + .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.postgresql..", "org.flywaydb.database.postgresql..") + .as( + "adapter:outbound:persistence-jpa (non-postgresql) must stay vendor-neutral: " + + "PostgreSQL driver / flyway-database-postgresql types live only in the " + + ".postgresql subpackage of adapter:outbound:persistence-jpa.") + .allowEmptyShould(true); + + // The same fence around the second vendor. H2 arrives as a runtimeOnly driver, so today nothing + // imports org.h2.. and this rule holds vacuously — which is the point of writing it now: the + // first class that reaches for an H2 type has to do it inside the vendor package, before the + // "vendor-neutral base" claim quietly stops being true. + @ArchTest + static final ArchRule PERSISTENCE_RDBMS_STAYS_NEUTRAL_OF_H2 = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") + .and() + .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.h2..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.h2..") + .as( + "adapter:outbound:persistence-jpa (non-h2) must stay vendor-neutral: H2 driver types " + + "live only in the .h2 subpackage of adapter:outbound:persistence-jpa.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS = + fields() + .that() + .areAnnotatedWith("jakarta.persistence.Column") + .and() + .areDeclaredInClassesThat() + .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") + .and() + .areDeclaredInClassesThat() + .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..") + .should(notDeclareColumnDefinition()) + .as( + "adapter:outbound:persistence-jpa (non-postgresql) must not pin vendor SQL " + + "columnDefinition strings; RDBMS physical types belong to vendor Flyway " + + "migrations, while rdbms entities may use portable JPA/Hibernate type hints.") + .allowEmptyShould(true); + + // NOTE: adapter.outbound.identifier AND adapter.outbound.persistence are both nested under + // adapter.outbound (sibling adapters, not top-level packages), so the rules below exclude each + // one's own package from the side that would otherwise match it against itself — mirroring the + // resideOutsideOfPackage sibling-exclusion style already used for + // PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL / PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES + // above. Without this, an intra-identifier or intra-persistence reference (a class referencing a + // class in its own nested adapter) would spuriously violate one of the rules. + + @ArchTest + static final ArchRule OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS = + noClasses() + .that() + .resideInAPackage("..adapter.outbound..") + .and() + .resideOutsideOfPackage("..adapter.outbound.identifier..") + // persistence-jpa is itself a nested outbound adapter; exclude its own package from the + // selector so its intra-references (which land in the ..adapter.outbound.persistence.. + // forbidden target below) do not self-overlap. Generic outbound adapters (messaging, + // cache, http, ...) are still forbidden from depending on persistence. + .and() + .resideOutsideOfPackage("..adapter.outbound.persistence..") + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "..adapter.inbound.web..", + "..adapter.outbound.persistence..", + "..adapter.outbound.identifier..", + "..bootstrap..") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP = + noClasses() + .that() + .resideInAPackage("..adapter.outbound.identifier..") + .should() + .dependOnClassesThat( + JavaClass.Predicates.resideInAnyPackage( + "..adapter.inbound.web..", + "..adapter.outbound.persistence..", + "..bootstrap..", + "org.springframework.data.repository..", + "org.springframework.data.jpa.repository..", + "jakarta.persistence..", + "javax.persistence..", + "org.hibernate..") + .or( + JavaClass.Predicates.resideInAPackage("..adapter.outbound..") + .and( + JavaClass.Predicates.resideOutsideOfPackage( + "..adapter.outbound.identifier..")))) + .as( + "adapter:outbound:identifier is a non-IO driven adapter (UUIDv7 generation/codec) —" + + " it must not reach into sibling adapters, persistence, or the composition root" + + " (feature-resource-identifier-contract §4 taxonomy).") + .allowEmptyShould(true); + + // ---- Task 7: explicit inbound/outbound adapter topology (hexagonal driving/driven split) ---- + // The moves in Tasks 1–6 established the new package topology; the three rules below make it an + // ENFORCED invariant rather than a convention. The inbound (driving) and outbound (driven) + // adapter families must never reach across to each other — every interaction between them is + // mediated by an application port — and the outbound concerns are peers that share code only + // through :adapter:outbound:support. These generalise the per-adapter isolation rules above + // (WEB_..., PERSISTENCE_..., OUTBOUND_..., IDENTIFIER_...) into the two family-level directions. + + @ArchTest + static final ArchRule INBOUND_ADAPTERS_DO_NOT_DEPEND_ON_OUTBOUND_ADAPTERS = + noClasses() + .that() + .resideInAPackage("..adapter.inbound..") + .should() + .dependOnClassesThat() + .resideInAPackage("..adapter.outbound..") + .as( + "inbound (driving) adapters must not depend on outbound (driven) adapters — an " + + "inbound adapter drives the application through its inbound ports and reaches " + + "persistence / messaging / cache / http only via application outbound ports, " + + "never by touching an outbound adapter directly (Task 7 adapter topology)"); + + @ArchTest + static final ArchRule OUTBOUND_ADAPTERS_DO_NOT_DEPEND_ON_INBOUND_ADAPTERS = + noClasses() + .that() + .resideInAPackage("..adapter.outbound..") + .should() + .dependOnClassesThat() + .resideInAPackage("..adapter.inbound..") + .as( + "outbound (driven) adapters must not depend on inbound (driving) adapters — a driven " + + "adapter implements an application outbound port and is oblivious to which " + + "inbound adapter (web today, a future messaging-inbound tomorrow) triggered " + + "the call (Task 7 adapter topology)"); + + @ArchTest + static final ArchRule OUTBOUND_ADAPTERS_ARE_PEERS_SHARING_ONLY_SUPPORT = + slices() + .matching("..adapter.outbound.(*)..") + .namingSlices("$1") + .should() + .notDependOnEachOther() + // :adapter:outbound:support is the shared base (OutboundCorrelation, fail-open + // dependency logging, the @Configuration seam) that every outbound adapter is allowed + // to build on — a dependency whose TARGET resides in support is the sanctioned + // shared-code path, not peer-to-peer coupling, so it is ignored. Every other + // cross-slice edge (e.g. messaging -> cache, persistence -> httpclient) is forbidden. + .ignoreDependency( + DescribedPredicate.alwaysTrue(), + JavaClass.Predicates.resideInAPackage("..adapter.outbound.support..")) + .as( + "outbound adapters (persistence / identifier / cache / httpclient / messaging / " + + "notification) are peers: none may depend on a sibling; shared outbound code " + + "lives in :adapter:outbound:support (Task 7 adapter topology)"); + + @ArchTest + static final ArchRule CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES = + noClasses() + .that(CleanArchitectureRuleSupport.ARE_CONTROLLERS) + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "..repository..", + "..adapter.outbound.persistence..", + "..adapter.outbound.persistence..entity..", + "org.springframework.data.repository..", + "org.springframework.data.jpa.repository..", + "jakarta.persistence..", + "javax.persistence.."); + + /** + * A datastore handle is a capability, and only the adapter that owns it may hold one. + * + *

The MongoDB guardrail existed as a set of forbidden type names with a test that asserted the + * set's contents. Nothing compared it to a class, so "a controller must not hold a MongoTemplate" + * was a passing test about a {@code Set} while every controller was governed by nothing — + * and Boot's own auto-configuration supplies a {@code MongoTemplate}, a {@code MongoClient} and a + * {@code MongoDatabase} as beans, so the injection was one constructor parameter away. + * + *

Checked here, at the composition root, because this is the only place where every production + * class is on one classpath: a rule that runs inside the Mongo leaf can only see the leaf, and + * the types it exists to keep out live everywhere else. + * + *

A driver handle bypasses the whole platform at once — collection profile, consistency + * profile, timeout budget, failure translation, observation — which is why the ban covers the + * driver packages rather than a list of class names that a new driver release can extend. + */ + @ArchTest + static final ArchRule ONLY_THE_PERSISTENCE_ADAPTER_HOLDS_A_MONGO_HANDLE = + noClasses() + .that() + .resideInAnyPackage( + "dev.caskeleton.domain..", + "dev.caskeleton.application..", + "dev.caskeleton.adapter.inbound..", + "dev.caskeleton.bootstrap..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("com.mongodb..", "org.springframework.data.mongodb..") + .as( + "only the MongoDB outbound adapter holds a MongoDB template, client, database or " + + "collection; every other layer reaches MongoDB through an application " + + "outbound port"); + + /** + * Which notification platform package may depend on which. + * + *

The design maps 31 logical modules onto packages inside five Gradle leaves, and the registry + * gate stops at the leaf boundary — so inside a leaf any package could reach any other and the + * layering existed only in the document. This table is the layering, and it is derived from the + * edges the code actually has: adding one means adding it here, in review, rather than + * discovering later that the module graph in the design and the import graph in the source + * disagree. + * + *

{@code api} depends on nothing, which is what makes it usable as the contract everything + * else shares. + */ + private static final Map> NOTIFICATION_PLATFORM_PACKAGE_EDGES = + Map.ofEntries( + Map.entry("api", Set.of()), + // port -> template: the template publication port speaks the platform's own template + // vocabulary — a slot, a published version — rather than restating it as strings. The + // edge + // is one-way; template depends on api alone, so nothing cycles through it. + Map.entry("port", Set.of("api", "template")), + // admin -> callback/dispatch/observation: the operator plane moved here from the outbound + // adapter, where operator authorization and transaction orchestration did not belong. + Map.entry( + "admin", Set.of("api", "callback", "dispatch", "observation", "policy", "provider")), + // callback -> port is an implementation depending on the interface it implements, which + // is the direction that makes the port a boundary. The reverse edge is what was removed. + Map.entry("callback", Set.of("api", "observation", "port")), + // dispatch -> port: the split inbound use cases live beside the service they wrap and + // implement the port interfaces callers depend on. + Map.entry( + "dispatch", + Set.of( + "api", + "port", + "callback", + "contact", + "observation", + "policy", + "provider", + "push", + "security", + "template")), + Map.entry("contact", Set.of("api")), + Map.entry("email", Set.of("api")), + Map.entry("inbox", Set.of("api")), + Map.entry("observation", Set.of("api")), + Map.entry("policy", Set.of("api", "contact", "provider")), + Map.entry("provider", Set.of("api", "callback", "security", "template")), + Map.entry("push", Set.of("api")), + Map.entry("security", Set.of("api", "contact")), + Map.entry("sms", Set.of("api", "contact")), + Map.entry("template", Set.of("api")), + Map.entry("webpush", Set.of("api"))); + + private static final String NOTIFICATION_PLATFORM_ROOT = + "dev.caskeleton.application.notification.platform"; + + @ArchTest + static final ArchRule NOTIFICATION_PLATFORM_PACKAGE_EDGES_ARE_REGISTERED = + classes() + .that() + .resideInAPackage(NOTIFICATION_PLATFORM_ROOT + "..") + .should(dependOnlyOnRegisteredNotificationPackages()) + .as( + "a notification platform package may depend only on the packages registered for it; " + + "the leaf-level registry cannot see inside a Gradle project, so the module " + + "graph is enforced here or nowhere (NTF-022)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NOTIFICATION_PLATFORM_PACKAGES_ARE_ACYCLIC = + slices() + .matching(NOTIFICATION_PLATFORM_ROOT + ".(*)..") + .namingSlices("$1") + .should() + .beFreeOfCycles() + .as( + "a cycle between two notification packages means neither can be understood, " + + "tested, or extracted without the other (NTF-022)"); + + @ArchTest + static final ArchRule NOTIFICATION_ADAPTER_PACKAGES_ARE_ACYCLIC = + slices() + .matching("dev.caskeleton.adapter.outbound.notification.platform.(*)..") + .namingSlices("$1") + .should() + .beFreeOfCycles() + .as("the outbound notification adapter's packages form a DAG too (NTF-022)"); + + /** + * Hold every notification platform package to its registered edge set. + * + * @return a condition that reports each unregistered package edge + */ + private static ArchCondition dependOnlyOnRegisteredNotificationPackages() { + return new ArchCondition<>("depend only on registered notification platform packages") { + @Override + public void check(JavaClass item, ConditionEvents events) { + String source = notificationPackageOf(item.getPackageName()); + if (source == null) { + return; + } + Set allowed = NOTIFICATION_PLATFORM_PACKAGE_EDGES.get(source); + if (allowed == null) { + events.add( + SimpleConditionEvent.violated( + item, + "notification platform package '" + + source + + "' is not registered in NOTIFICATION_PLATFORM_PACKAGE_EDGES: " + + item.getName())); + return; + } + for (JavaClass target : + item.getDirectDependenciesFromSelf().stream() + .map(dependency -> dependency.getTargetClass()) + .toList()) { + String edge = notificationPackageOf(target.getPackageName()); + if (edge == null || edge.equals(source) || allowed.contains(edge)) { + continue; + } + events.add( + SimpleConditionEvent.violated( + item, + "unregistered notification platform package edge " + + source + + " -> " + + edge + + " (" + + item.getName() + + " -> " + + target.getName() + + ")")); + } + } + }; + } + + /** + * The notification platform sub-package a class belongs to. + * + * @param packageName the class's package + * @return the first segment below the platform root, or null when the class is outside it + */ + private static String notificationPackageOf(String packageName) { + if (!packageName.startsWith(NOTIFICATION_PLATFORM_ROOT + ".")) { + return null; + } + String remainder = packageName.substring(NOTIFICATION_PLATFORM_ROOT.length() + 1); + int dot = remainder.indexOf('.'); + return dot < 0 ? remainder : remainder.substring(0, dot); + } + + private static ArchCondition notDeclareColumnDefinition() { + return new ArchCondition<>("not declare @Column columnDefinition") { + @Override + public void check(JavaField field, ConditionEvents events) { + field + .tryGetAnnotationOfType("jakarta.persistence.Column") + .ifPresent( + column -> { + String columnDefinition = + column.get("columnDefinition").map(Object::toString).orElse(""); + if (!columnDefinition.isBlank()) { + events.add( + SimpleConditionEvent.violated( + field, + "Field " + + field.getFullName() + + " pins vendor SQL columnDefinition='" + + columnDefinition + + "' in adapter:outbound:persistence-jpa; move the " + + "physical type to the vendor migration.")); + } + }); + } + }; + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java new file mode 100644 index 00000000..a0008fac --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ApplicationBoundaryArchitectureTest.java @@ -0,0 +1,841 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaAnnotation; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaEnumConstant; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaMethodCall; +import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import dev.caskeleton.application.capability.UseCaseCapability; +import dev.caskeleton.application.security.RequiresPermission; +import dev.caskeleton.application.usecase.CommandUseCase; +import dev.caskeleton.application.usecase.QueryUseCase; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.boot.health.actuate.endpoint.HealthEndpoint; +import org.springframework.boot.health.contributor.Health; +import org.springframework.boot.health.contributor.HealthIndicator; + +/** + * What {@code application-core} may depend on, and how a use case declares the capability it needs. + * The two questions live together because the answer to the second is what makes the first + * enforceable: a use case that names its capability can be checked against the ports it actually + * touches. + * + *

application-core 가 무엇에 의존해도 되는가, 그리고 유스케이스가 자기 능력을 어떻게 선언하는가. + * + *

{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by + * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields + * inside a nested class — the rules would have stopped running while the build stayed green + * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share + * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest} + * holds the full rule inventory so none can be lost in a move like this one. + */ +@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class) +class ApplicationBoundaryArchitectureTest { + + @ArchTest + static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT = + noClasses() + .that() + .resideInAPackage("..application..") + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "..adapter..", + "..bootstrap..", + "..infra..", + "..presentation..", + "..cmd..", + "jakarta.persistence..", + "javax.persistence..", + "jakarta.servlet..", + "org.springframework.web..", + "org.hibernate..") + .allowEmptyShould(true); + + /** + * The application never names the messaging platform's types. + * + *

The new platform has its own {@code MessagePublisher}, {@code MessageEnvelope} and {@code + * OutboxStatus}, and the application already has a canonical publish port and outbox model of its + * own. An import from application code would make the platform's vocabulary the business boundary + * — and its {@code OutboxStatus.FAILED} means the opposite of the legacy {@code + * OutboxEventStatus.FAILED}, so the two models cannot be mixed by name without inverting + * retryable and terminal. The translation belongs in an outbound bridge adapter. + */ + @ArchTest + static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM = + noClasses() + .that() + .resideInAPackage("..application..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("dev.caskeleton.messaging..") + .because( + "APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM: the application owns its " + + "publish port and outbox model; a bridge adapter translates, and the two " + + "outbox status models mean opposite things under the same names") + .allowEmptyShould(true); + + /** + * The two notification models must not learn about each other. + * + *

NOTIF-ADR-005 makes the platform canonical and keeps R1's hundred public types in place + * without new consumers. Nothing enforced that: {@code Channel}, plan, dispatch and + * receipt/evidence exist in both namespaces with the same simple names, so a new consumer picks + * one by import order and the two models drift apart while both claim to be current. + * + *

The exception is a single compatibility package. If a bridge is ever needed it goes there, + * and only there — an exception in several places is not an exception, it is the second canonical + * model the ADR exists to prevent. + */ + @ArchTest + static final ArchRule NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.application.notification.platform..") + .and() + .resideOutsideOfPackage("dev.caskeleton.application.notification.compatibility.r1..") + .should() + .dependOnClassesThat( + new com.tngtech.archunit.base.DescribedPredicate<>( + "reside in the R1 notification namespace") { + @Override + public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { + String name = candidate.getPackageName(); + return name.equals("dev.caskeleton.application.notification"); + } + }) + .because( + "NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER: NOTIF-ADR-005 makes the " + + "platform canonical; a bridge belongs in " + + "dev.caskeleton.application.notification.compatibility.r1 and nowhere else") + .allowEmptyShould(true); + + /** + * An orchestration entrypoint, recognised by what it does rather than by what it implements. + * + *

This is the gap NTF-019 names. Every existing capability rule starts from {@code + * .that().implement(CommandUseCase.class)} — so a new orchestration that simply never implements + * the marker is not checked by any of them. The rules were not wrong; they were unreachable. A + * class that opens a transaction and writes through a repository port is an orchestration + * entrypoint whether or not anybody remembered to say so. + */ + private static final DescribedPredicate ARE_ORCHESTRATION_ENTRYPOINTS = + new DescribedPredicate<>("application orchestration entrypoints") { + @Override + public boolean test(JavaClass candidate) { + // ".application." rather than a prefix match, matching the glob convention the rest of + // this file uses — and, concretely, so the negative fixture that proves this rule fires + // is itself inside the rule's scope. A rule whose only corpus is production code cannot + // be shown to fire at all. + if (!candidate.getPackageName().contains(".application.") + || candidate.isInterface() + || candidate.getModifiers().contains(JavaModifier.ABSTRACT) + || candidate.isRecord() + || candidate.isEnum()) { + return false; + } + // Holding a transaction port is the tell. A helper that only computes has no boundary + // to declare; a class that can start a unit of work is deciding its shape. + // + // Read from the field types rather than from the dependency graph: an isolated corpus + // (the negative fixture) resolves the port as a stub, and the dependency edge is then + // absent — which would make this rule pass on the very shape it exists to catch. + return candidate.getFields().stream() + .anyMatch( + field -> + field + .getRawType() + .getFullName() + .equals("dev.caskeleton.application.transaction.TransactionPort")); + } + }; + + @ArchTest + static final ArchRule ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY = + classes() + .that(ARE_ORCHESTRATION_ENTRYPOINTS) + .and() + .haveSimpleNameEndingWith("UseCase") + .should() + .beAnnotatedWith(UseCaseCapability.class) + .as( + "NTF-019: a use case that opens a transaction declares its capability. The existing " + + "rules all start from the marker interface, so an entrypoint that never " + + "implements it is checked by nothing at all") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule OUTBOUND_NOTIFICATION_HOLDS_NO_ADMIN_POLICY = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.adapter.outbound.notification..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName("dev.caskeleton.application.transaction.TransactionPort") + .orShould() + .dependOnClassesThat() + .haveFullyQualifiedName( + "dev.caskeleton.application.notification.platform.admin.AdminActor") + .orShould() + .dependOnClassesThat() + .haveFullyQualifiedName( + "dev.caskeleton.application.notification.platform.admin.NotificationAdminAuthority") + .as( + "NTF-020: who may perform an operator action, and where a unit of work begins, are " + + "decisions about the product. They were both made in the layer whose job is " + + "speaking provider protocols, where no application test could reach them") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_A_CAPABILITY = + classes() + .that( + new DescribedPredicate("implement an inbound port") { + @Override + public boolean test(JavaClass candidate) { + return !candidate.isInterface() + && !candidate.getModifiers().contains(JavaModifier.ABSTRACT) + && candidate.getAllRawInterfaces().stream() + .anyMatch( + implemented -> implemented.getPackageName().contains(".port.in")); + } + }) + .should() + .beAnnotatedWith(UseCaseCapability.class) + .as( + "NTF-019: implementing an inbound port is what makes a class an entrypoint, whatever " + + "it is named. The name-based half of this check would miss a class called " + + "NotificationSubmissionService that a controller reaches directly") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule USE_CASE_PORTS_CARRY_ONE_OPERATION = + classes() + .that() + .resideInAPackage("..application..port.in..") + .and() + .areInterfaces() + .should(declareAtMostOneAbstractOperation()) + .as( + "NTF-019: one operation per inbound port. NotificationOrchestrator mixed submit, " + + "schedule, cancel and get, and no single transaction mode is true of all four " + + "— the compiler agrees, since CommandUseCase cannot be inherited twice with " + + "different type arguments") + .allowEmptyShould(true); + + /** + * Hold an inbound port to a single abstract operation. + * + * @return the condition + */ + private static ArchCondition declareAtMostOneAbstractOperation() { + return new ArchCondition<>("declare at most one abstract operation") { + @Override + public void check(JavaClass item, ConditionEvents events) { + List abstractMethods = + item.getMethods().stream() + .filter(method -> !method.getModifiers().contains(JavaModifier.STATIC)) + .filter(method -> method.getModifiers().contains(JavaModifier.ABSTRACT)) + .toList(); + if (abstractMethods.size() > 1) { + events.add( + SimpleConditionEvent.violated( + item, + item.getName() + + " declares " + + abstractMethods.size() + + " operations; an inbound port declares one, so its capability can be " + + "stated honestly")); + } + } + }; + } + + @ArchTest + static final ArchRule APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.application..") + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "org.slf4j..", + "java.util.logging..", + "ch.qos.logback..", + "org.apache.logging.log4j..", + "io.micrometer..") + .as( + "APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK: application-core owns typed outbound " + + "diagnostic ports, while logging and metrics implementations belong to " + + "outbound adapters") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule SAMPLE_APPLICATION_HAS_NO_SLF4J = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.sample.portfolio.application..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.slf4j..") + .as( + "SAMPLE_APPLICATION_HAS_NO_SLF4J: sample application collaborators read " + + "correlation context through application-core CorrelationIdPort; inbound " + + "adapters own MDC") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.sample.portfolio.application..") + .and() + .doNotHaveFullyQualifiedName( + "dev.caskeleton.sample.portfolio.application.poster.UploadPosterImageUseCase") + .should() + .dependOnClassesThat() + .resideInAPackage("dev.caskeleton.application.storage") + .as( + "Only the characterized UploadPosterImageUseCase may temporarily import the legacy " + + "ObjectStoragePort/StoredObject contract. New sample business code must use " + + "the semantic objectstorage ports. Task 23 moves compatibility into the exact " + + "legacy slice; Task 24 separately reviews the administrative migration seam.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION = + noClasses() + .that() + .resideInAPackage("..application..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional") + .as( + "application package must use TransactionPort instead of " + + "@Transactional (feature-application-port-usecase-contract D3)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE = + classes() + .that() + .implement(CommandUseCase.class) + .or() + .implement(QueryUseCase.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should() + .haveSimpleNameEndingWith("UseCase") + .as( + "CommandUseCase / QueryUseCase implementations must end with 'UseCase' " + + "(feature-application-port-usecase-contract D1)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY = + classes() + .that() + .implement(CommandUseCase.class) + .or() + .implement(QueryUseCase.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should() + .beAnnotatedWith(UseCaseCapability.class) + .as( + "every CommandUseCase / QueryUseCase implementation must declare " + + "@UseCaseCapability (feature-application-port-usecase-contract §판정 기준)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT = + noClasses() + .that() + .resideInAPackage("..application..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName("org.springframework.context.ApplicationContext") + .as( + "application package must not depend on Spring ApplicationContext " + + "(blocks getBean(Class) reflection-style bypass — " + + "feature-architecture-enforcement-rules D11). string-key bean " + + "lookup / Class.forName(String) bypass remains ArchUnit's " + + "static-analysis limit per D12.") + .allowEmptyShould(true); + + // NOTE: the former `inbound_port_implementations_do_not_declare_keyed_idempotency` + // freeze rule (feature-application-port-usecase-contract D14) is removed now that + // feature-rate-limit-idempotency-contract has merged: Idempotency.KEYED is backed + // by IdempotencyExecutor + IdempotencyStorePort + the idempotency_record table, so a + // use case may declare it. + + // ---- feature-repository-access-permission-contract D12 / D6 (repositoryAccess coherence) ---- + + @ArchTest + static final ArchRule READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS = + classes() + .that() + .areAnnotatedWith(UseCaseCapability.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should(notCallRepositoryWriteUnlessWriteRepository()) + .as( + "D12 READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS: a use case " + + "whose @UseCaseCapability(repositoryAccess) is not WRITE_REPOSITORY must " + + "not DIRECTLY call a write method " + + "(save / saveAll / saveAndFlush / delete* / remove* / update / insert / " + + "persist / merge / store) of a *Repository port. Declaring READ_REPOSITORY " + + "(or NONE) then mutating is the silent read-to-write upgrade this contract " + + "blocks (feature-repository-access-permission-contract D12). " + + "UNSUPPORTED_IMPL_DECISION: ArchUnit static analysis only reaches DIRECT " + + "calls — a write reached through a helper / mapper escapes the call graph " + + "and stays a code-review concern (D12 static-analysis limit). The write " + + "method-name allowlist is a ca-tmpl convention; extend it when a new " + + "mutating verb enters the repository vocabulary.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS = + classes() + .that() + .areAnnotatedWith(UseCaseCapability.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should(haveWriteRepositoryAccessWhenBulkWrite()) + .as( + "D6 BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS: " + + "@UseCaseCapability(bulkWrite = true) requires repositoryAccess = " + + "WRITE_REPOSITORY — a bulk write (N > 100) is still a write, and the " + + "capability registry binds BULK_WRITE to WRITE_REPOSITORY " + + "(feature-repository-access-permission-contract D6).") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY = + classes() + .that() + .areAnnotatedWith(UseCaseCapability.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should(callTransactionPortMethodRequiredByCapability()) + .as( + "feature-domain-feature-onboarding-contract D4: a use case that declares a " + + "repository-backed transaction capability must call the matching " + + "TransactionPort boundary directly: READ_REPOSITORY+READ_ONLY -> inRead, " + + "WRITE_REPOSITORY+WRITE -> inWrite or inRootWrite, REQUIRES_NEW -> inNew. " + + "RepositoryAccess.NONE may intentionally skip a DB transaction. " + + "UNSUPPORTED_IMPL_DECISION: static analysis reaches direct calls only; a " + + "transaction hidden behind a helper remains a code-review concern.") + .allowEmptyShould(true); + + // ---- feature-authentication-authorization-contract D1 / D4 (product authorization) ---- + + @ArchTest + static final ArchRule MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION = + classes() + .that() + .areAnnotatedWith(UseCaseCapability.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should(declareRequiredPermissionWhenMutating()) + .as( + "D4 MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION: a use case whose " + + "@UseCaseCapability(repositoryAccess) is WRITE_REPOSITORY MUST also declare " + + "@RequiresPermission, so a mutating operation cannot silently ship without a " + + "permission gate (feature-authentication-authorization-contract D4). The " + + "skeleton baseline is mutating-only; public reads are exempt. " + + "UNSUPPORTED_IMPL_DECISION: this rule checks the type-level annotation (the " + + "@UseCaseCapability convention); a method-only @RequiresPermission on a use " + + "case still trips it. AOP self-invocation / non-bean entry points stay a " + + "static-analysis blind spot (D4 limit).") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY = + noClasses() + .that() + .resideInAnyPackage("..application..", "..domain..") + .should() + .dependOnClassesThat() + .resideInAPackage("org.springframework.security..") + .as( + "D1 APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY: application-core and " + + "domain-core must stay free of Spring Security types. The authorization " + + "DECISION is a framework-free port (@RequiresPermission / AuthorizationPort / " + + "AuthorizationPrincipal); the Spring-aware enforcement MECHANISM " + + "(AuthorizationManager) lives only in adapter:inbound:web " + + "(feature-authentication-authorization-contract D1).") + .allowEmptyShould(true); + + // ---- feature-management-actuator-security-contract D6 (health shape-ownership guardrail) ---- + // ManagementSecurityConfig (in ..management.security..) must NOT import or depend on + // HealthEndpoint, HealthIndicator, or Health — the health endpoint shape belongs to + // the runtime-health branch. The security config only + // CleanArchitectureRuleSupport.matches/permits endpoints by id string; + // it never inspects health internals. + + @ArchTest + static final ArchRule MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS = + noClasses() + .that() + .resideInAPackage("..management.security..") + .should() + .dependOnClassesThat() + .areAssignableTo(HealthEndpoint.class) + .orShould() + .dependOnClassesThat() + .areAssignableTo(HealthIndicator.class) + .orShould() + .dependOnClassesThat() + .areAssignableTo(Health.class) + .as( + "D6 MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS: classes in " + + "..management.security.. must not import or depend on HealthEndpoint, " + + "HealthIndicator, or Health — the health endpoint shape is owned " + + "by the runtime-health branch. The actuator security config CleanArchitectureRuleSupport.matches " + + "endpoints by id only, never by health internals " + + "(feature-management-actuator-security-contract D6).") + .allowEmptyShould(true); + + private static final Set REPOSITORY_WRITE_METHOD_NAMES = + Set.of( + "save", + "saveAll", + "saveAndFlush", + "delete", + "deleteById", + "deleteAll", + "remove", + "removeAll", + "removeById", + "update", + "insert", + "persist", + "merge", + "store"); + + private static ArchCondition notCallRepositoryWriteUnlessWriteRepository() { + return new ArchCondition<>( + "not call a *Repository write method unless " + "repositoryAccess = WRITE_REPOSITORY") { + @Override + public void check(JavaClass item, ConditionEvents events) { + JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); + Object repositoryAccess = annotation.get("repositoryAccess").orElse(null); + boolean writeRepository = + repositoryAccess instanceof JavaEnumConstant constant + && "WRITE_REPOSITORY".equals(constant.name()); + if (writeRepository) { + return; // WRITE_REPOSITORY use cases are permitted to mutate. + } + for (JavaMethodCall call : item.getMethodCallsFromSelf()) { + String targetOwner = call.getTargetOwner().getSimpleName(); + String targetName = call.getName(); + if (targetOwner.endsWith("Repository") + && REPOSITORY_WRITE_METHOD_NAMES.contains(targetName)) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " declares repositoryAccess != " + + "WRITE_REPOSITORY but directly calls " + + targetOwner + + "." + + targetName + + "(...) — a repository write " + + "(feature-repository-access-permission-contract D12)")); + } + } + } + }; + } + + private static ArchCondition declareRequiredPermissionWhenMutating() { + return new ArchCondition<>( + "declare @RequiresPermission when repositoryAccess = WRITE_REPOSITORY") { + @Override + public void check(JavaClass item, ConditionEvents events) { + JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); + Object repositoryAccess = annotation.get("repositoryAccess").orElse(null); + boolean writeRepository = + repositoryAccess instanceof JavaEnumConstant constant + && "WRITE_REPOSITORY".equals(constant.name()); + if (!writeRepository) { + return; + } + if (!item.isAnnotatedWith(RequiresPermission.class.getName())) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " declares @UseCaseCapability(repositoryAccess = " + + "WRITE_REPOSITORY) but is missing @RequiresPermission — a mutating use " + + "case must declare the permission it requires " + + "(feature-authentication-authorization-contract D4)")); + } + } + }; + } + + private static ArchCondition haveWriteRepositoryAccessWhenBulkWrite() { + return new ArchCondition<>( + "declare repositoryAccess = WRITE_REPOSITORY when bulkWrite = true") { + @Override + public void check(JavaClass item, ConditionEvents events) { + JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); + boolean bulkWrite = Boolean.TRUE.equals(annotation.get("bulkWrite").orElse(false)); + if (!bulkWrite) { + return; + } + Object repositoryAccess = annotation.get("repositoryAccess").orElse(null); + boolean writeRepository = + repositoryAccess instanceof JavaEnumConstant constant + && "WRITE_REPOSITORY".equals(constant.name()); + if (!writeRepository) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " declares @UseCaseCapability(bulkWrite = true) " + + "without repositoryAccess = WRITE_REPOSITORY " + + "(feature-repository-access-permission-contract D6)")); + } + } + }; + } + + @SuppressWarnings("resource") + private static ArchCondition callTransactionPortMethodRequiredByCapability() { + return new ArchCondition<>("call the TransactionPort method required by @UseCaseCapability") { + @Override + public void check(JavaClass item, ConditionEvents events) { + JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); + String transactionMode = enumAnnotationValue(annotation, "transactionMode"); + String repositoryAccess = enumAnnotationValue(annotation, "repositoryAccess"); + + Set requiredMethods = Set.of(); + if ("REQUIRES_NEW".equals(transactionMode)) { + requiredMethods = Set.of("inNew"); + } else if ("WRITE".equals(transactionMode) && "WRITE_REPOSITORY".equals(repositoryAccess)) { + requiredMethods = Set.of("inWrite", "inRootWrite"); + } else if ("READ_ONLY".equals(transactionMode) + && "READ_REPOSITORY".equals(repositoryAccess)) { + requiredMethods = Set.of("inRead"); + } + + if (requiredMethods.isEmpty()) { + return; + } + + for (JavaMethodCall call : item.getMethodCallsFromSelf()) { + if ("dev.caskeleton.application.transaction.TransactionPort" + .equals(call.getTargetOwner().getFullName()) + && requiredMethods.contains(call.getName())) { + return; + } + } + + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " declares @UseCaseCapability(transactionMode = " + + transactionMode + + ", repositoryAccess = " + + repositoryAccess + + ") but does not directly call one of TransactionPort." + + requiredMethods + + "(...)")); + } + }; + } + + private static String enumAnnotationValue(JavaAnnotation annotation, String property) { + Object value = annotation.get(property).orElse(null); + return value instanceof JavaEnumConstant constant ? constant.name() : ""; + } + + // ---- feature-repository-access-permission-contract D7 (outbound capability coherence) ---- + + /** + * Outbound ports are the interfaces a use case must hold {@code externalOutboundAllowed} to call. + * They are identified by their adapter binding — any interface implemented by a production class + * residing under {@code ..adapter.outbound..} (e.g. RepoStatsPort ← RepoStatsPortClient), + * EXCLUDING the {@code ..adapter.outbound.identifier..} sub-package: UUIDv7 id/event-id factories + * are a non-IO driven adapter (in-process generation/codec, no external HTTP / message / cache + * call — see IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP), so calling one + * directly does not require {@code externalOutboundAllowed = true}. Also EXCLUDING the {@code + * ..adapter.outbound.persistence..} sub-package: repositories/stores are internal (driven) + * outbound adapters — a use case calling a repository does not require {@code + * externalOutboundAllowed = true}, and their package moved under {@code adapter.outbound} only as + * a topology rename (Task 7 owns the holistic external-vs-internal split). Precomputed from + * production classes (tests excluded) so the coherence rule has no application-side marker + * dependency. + */ + private static final Set OUTBOUND_PORT_NAMES = outboundPortNames(); + + private static Set outboundPortNames() { + JavaClasses production = + new ClassFileImporter() + .withImportOption(new ProductionClassImportOption()) + .importPackages("dev.caskeleton"); + Set names = new HashSet<>(); + for (JavaClass type : production) { + if (type.getPackageName().contains(".adapter.outbound.") + && !isInProcessDrivenOutbound(type.getPackageName())) { + for (JavaClass iface : type.getAllRawInterfaces()) { + names.add(iface.getFullName()); + } + } + } + return names; + } + + /** + * True when {@code packageName} is an IN-PROCESS (non-IO) driven outbound concern — {@code + * ..adapter.outbound.persistence..} (repositories / stores) or {@code + * ..adapter.outbound.identifier..} (UUIDv7 id/event-id generation & codec). Both nest under + * {@code ..adapter.outbound..} as a Task-1–6 topology rename but perform NO external IO (no HTTP + * / message / cache network call), so an interface they implement is NOT an external outbound + * port and a use case calling one directly does not require {@code externalOutboundAllowed = + * true}. This single predicate consolidates the two per-package {@code .contains(...)} exclusions + * that Tasks 2/6 added locally to {@link #outboundPortNames()}. + * + *

The genuinely external-IO outbound adapters — {@code httpclient}, {@code messaging}, {@code + * notification}, and {@code cache} — are deliberately NOT treated as in-process here: a Redis + * cache call is a real network dependency, so the D7 external-outbound gate must keep covering + * it. Folding cache/support into this exclusion (as an earlier plan sketch suggested) would + * weaken that gate for no current benefit and contradict this heuristic's own IO-vs-in- process + * rationale, so it is intentionally NOT done. + */ + private static boolean isInProcessDrivenOutbound(String packageName) { + return packageName.contains(".adapter.outbound.persistence") + || packageName.contains(".adapter.outbound.identifier"); + } + + @ArchTest + static final ArchRule EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY = + classes() + .that() + .areAnnotatedWith(UseCaseCapability.class) + .and() + .areNotInterfaces() + .and() + .areNotAnonymousClasses() + .and() + .areNotMemberClasses() + .should(notCallOutboundPortUnlessAllowed()) + .as( + "D7 EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY: a " + + "use case that DIRECTLY calls an outbound port (an interface implemented " + + "by a production class under ..adapter.outbound..) must declare " + + "@UseCaseCapability(externalOutboundAllowed = true). Without it, emitting " + + "an external HTTP / message / cache call is forbidden " + + "(feature-repository-access-permission-contract D7). Outbound ports are " + + "identified by their adapter binding (RepoStatsPort ← RepoStatsPortClient), " + + "so no application-side marker is required. UNSUPPORTED_IMPL_DECISION: " + + "ArchUnit reaches DIRECT calls only — a call routed through a helper / " + + "mapper escapes the call graph and stays a code-review concern " + + "(static-analysis limit, mirrors D12).") + .allowEmptyShould(true); + + private static ArchCondition notCallOutboundPortUnlessAllowed() { + return new ArchCondition<>("not call an outbound port unless externalOutboundAllowed = true") { + @Override + public void check(JavaClass item, ConditionEvents events) { + JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); + boolean allowed = + Boolean.TRUE.equals(annotation.get("externalOutboundAllowed").orElse(false)); + if (allowed) { + return; // externalOutboundAllowed use cases may emit outbound calls. + } + for (JavaMethodCall call : item.getMethodCallsFromSelf()) { + String targetOwner = call.getTargetOwner().getFullName(); + if (OUTBOUND_PORT_NAMES.contains(targetOwner)) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " declares externalOutboundAllowed = " + + "false but directly calls outbound port " + + targetOwner + + " (feature-repository-access-permission-contract D7)")); + } + } + } + }; + } + + @ArchTest + static final ArchRule APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS = + methods() + .that() + .areDeclaredInClassesThat() + .resideInAPackage("..application..") + .and() + .arePublic() + .should() + .notHaveRawParameterTypes( + DescribedPredicate.describe( + "any in ..adapter.inbound.web..dto..", + (List params) -> { + for (JavaClass p : params) { + if (JavaClass.Predicates.resideInAPackage("..adapter.inbound.web..dto..") + .test(p)) { + return true; + } + } + return false; + })) + .as( + "application methods must not accept request/response DTOs — " + + "controllers must translate to Command/Query before invoking the use case " + + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)") + .allowEmptyShould(true); +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java new file mode 100644 index 00000000..8dadbe46 --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchRuleDiscoveryContractTest.java @@ -0,0 +1,235 @@ +package dev.caskeleton.bootstrap.architecture; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Every declared {@code @ArchTest} rule is actually discovered and run (BOOT-012). + * + *

This exists because of what happened when the obvious tidy-up was attempted. {@code + * CleanArchitectureTest} was one 2,792-line class holding 75 of this package's 87 rules, and the + * natural fix looked like grouping them into nested classes. A throwaway probe established that + * ArchUnit's test engine does not discover {@code @ArchTest} fields inside a nested class + * of an {@code @AnalyzeClasses} class: the build went green, no test report was produced, and the + * rule simply never ran. + * + *

That is the worst failure a verification suite can have. Splitting the class that way would + * have silently switched off 75 architecture rules while every build stayed green — and nothing in + * the repository would have noticed, because a rule that does not run reports no violation. + * + *

With this guard in place the split was then done the safe way: five top-level + * {@code @AnalyzeClasses} suites — domain purity, application boundary, adapter boundary, type + * boundary, forbidden APIs — plus {@code CleanArchitectureRuleSupport} for the two helpers more + * than one of them uses. This test is what proved nothing was lost in the move. + * + *

So the split is not the thing to guard; the guard is the thing to build first. This test pins + * both halves of the contract: + * + *

    + *
  1. the exact set of rule names that exist, so deleting one is a decision rather than an edit; + *
  2. that no rule is declared where the engine cannot see it, so a future reorganisation fails + * loudly instead of quietly disarming the suite. + *
+ * + *

With this in place the class can be split — into separate top-level + * {@code @AnalyzeClasses} classes, which share ArchUnit's import cache because the annotation + * values match, and which this test verifies still carry every rule. + */ +class ArchRuleDiscoveryContractTest { + + /** + * {@code @ArchTest} followed by the field declaration, with the indentation that precedes it. + * + *

Comment lines between the annotation and the declaration are skipped, and that is not a + * cosmetic allowance: the first version of this pattern required the declaration on the next line + * and therefore did not see {@code SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES}, + * whose nine-line "known defect" note sits in between. A rule could have hidden from this guard + * by being commented — which is precisely the kind of rule most worth keeping an eye on. + */ + private static final Pattern ARCH_TEST_FIELD = + Pattern.compile( + "(?m)^([ \\t]*)@ArchTest[ \\t]*\\n" + + "(?:[ \\t]*(?://[^\\n]*|/\\*(?:[^*]|\\*(?!/))*\\*/)[ \\t]*\\n)*" + + "[ \\t]*(?:static\\s+)?final\\s+\\w+\\s+(\\w+)"); + + /** + * Every rule the architecture suite declares today. + * + *

Every {@code @AnalyzeClasses} class in this package, not just the large one — the guard is + * about rules being discovered, and that is not a property of any single file. + * + *

Pinned by name rather than counted. A count would be satisfied by deleting one rule and + * adding another, which is exactly the substitution a reviewer would want to see spelled out. + */ + private static final List DECLARED_RULES = + List.of( + "AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC", + "APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY", + "APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT", + "APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT", + "APPLICATION_DOES_NOT_DEPEND_ON_OPTIONAL_ADAPTERS", + "APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM", + "APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION", + "APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK", + "APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS", + "BOOTSTRAP_USES_ONLY_THE_PERSISTENCE_EXPORT_SURFACE", + "BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS", + "COMPOSITION_ROOT_DOES_NOT_IMPLEMENT_APPLICATION_OR_DOMAIN_PORTS", + "CONFIGURATION_PROPERTIES_END_WITH_SETTINGS_OR_POLICY", + "CONTACT_POINT_VALUES_ARE_IMMUTABLE_RECORDS", + "CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES", + "CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES", + "CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122", + "DOMAIN_CONTEXT_PROPAGATION_PRIMITIVES_STAY_UNSHIPPED", + "DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS", + "DOMAIN_EVENTS_ARE_RECORDS", + "DOMAIN_EVENTS_ARE_TRANSPORT_FREE", + "DOMAIN_HAS_NO_LOGGER", + "DOMAIN_IS_PURE", + "EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY", + "IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP", + "INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES", + "INBOUND_ADAPTERS_DO_NOT_DEPEND_ON_OUTBOUND_ADAPTERS", + "INBOUND_PORT_IMPLEMENTATIONS_DECLARE_A_CAPABILITY", + "INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY", + "INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE", + "MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS", + "MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES", + "MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS", + "MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION", + "NOTIFICATION_ADAPTER_DOES_NOT_REACH_INTO_PERSISTENCE_OR_TRANSPORT", + "NOTIFICATION_ADAPTER_PACKAGES_ARE_ACYCLIC", + "NOTIFICATION_APPLICATION_CONTRACT_HAS_NO_PROVIDER_SDK", + "NOTIFICATION_APPLICATION_CONTRACT_HAS_NO_TRANSPORT_OR_PERSISTENCE", + "NOTIFICATION_CONTRACT_HAS_NO_DIAGNOSTIC_FRAMEWORK", + "NOTIFICATION_PLATFORM_PACKAGES_ARE_ACYCLIC", + "NOTIFICATION_PLATFORM_PACKAGE_EDGES_ARE_REGISTERED", + "NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER", + "NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR", + "NO_INHERITABLE_THREAD_LOCAL", + "NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL", + "NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR", + "NO_LONG_ID_PK", + "NO_MATH_RANDOM_FOR_ID", + "NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING", + "NO_PROBLEM_DETAIL_USAGE", + "NO_REFRESH_SCOPE_ANYWHERE", + "NO_RESPONSE_BODY_EMITTER", + "NO_SSE_EMITTER", + "NO_UUID_RANDOM_IN_CONTROLLER", + "NO_VARCHAR_255_FOR_ID_COLUMN", + "NO_WEBSOCKET_HANDLER", + "ONLY_THE_PERSISTENCE_ADAPTER_HOLDS_A_MONGO_HANDLE", + "ONLY_THE_SECURITY_PACKAGE_HANDLES_KEY_MATERIAL", + "OPTIONAL_ADAPTER_BEANS_ARE_GATED_BY_CONDITIONAL_ON_PROPERTY", + "ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY", + "OUTBOUND_ADAPTERS_ARE_PEERS_SHARING_ONLY_SUPPORT", + "OUTBOUND_ADAPTERS_DO_NOT_DEPEND_ON_INBOUND_ADAPTERS", + "OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS", + "OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES", + "OUTBOUND_NOTIFICATION_HOLDS_NO_ADMIN_POLICY", + "PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS", + "PERSISTENCE_DOES_NOT_DEPEND_ON_APPLICATION_SERVICES", + "PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER", + "PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS", + "PERSISTENCE_RDBMS_STAYS_NEUTRAL_OF_H2", + "PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO", + "PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES", + "QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES", + "READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS", + "REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS", + "SAMPLE_APPLICATION_HAS_NO_SLF4J", + "SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN", + "SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES", + "STARTUP_FAILURES_ARE_RAISED_THROUGH_THE_TYPED_FACTORY", + "THROWABLES_ARE_NAMED_EXCEPTION", + "USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY", + "USE_CASE_PORTS_CARRY_ONE_OPERATION", + "VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY", + "VALID_CASCADE_DEPTH_AT_MOST_THREE", + "VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR", + "WEB_ADAPTER_DOES_NOT_DEPEND_ON_PERSISTENCE_OR_OUTBOUND_ADAPTERS", + "WEB_DTOS_STAY_IN_WEB_ADAPTER"); + + @Test + @DisplayName("the architecture suite declares exactly the pinned set of rules") + void theSuiteDeclaresThePinnedRules() throws IOException { + assertThat(new TreeSet<>(declaredRules().keySet())) + .as( + "a rule that disappears from this set stopped constraining the codebase. Removing one " + + "is a decision — make it here, with the reason, rather than by deleting a field " + + "(BOOT-012).") + .containsExactlyElementsOf(new TreeSet<>(DECLARED_RULES)); + } + + @Test + @DisplayName("no rule is declared where ArchUnit's engine cannot discover it") + void noRuleIsDeclaredInsideANestedClass() throws IOException { + List undiscoverable = new ArrayList<>(); + declaredRules() + .forEach( + (rule, indentation) -> { + if (indentation > 2) { + undiscoverable.add(rule); + } + }); + + assertThat(undiscoverable) + .as( + "ArchUnit's engine does not discover @ArchTest fields inside a nested class — the build " + + "goes green, no report is written, and the rule never runs. A rule indented deeper " + + "than a top-level class member is almost certainly in one. Split the suite into " + + "separate top-level @AnalyzeClasses classes instead; they share the import cache " + + "(BOOT-012).") + .isEmpty(); + } + + /** Rule name to the indentation depth of its {@code @ArchTest} annotation, across the package. */ + private static Map declaredRules() throws IOException { + Map rules = new LinkedHashMap<>(); + Path architecture = + repositoryRoot() + .resolve("src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture"); + try (Stream sources = Files.walk(architecture)) { + for (Path source : sources.filter(p -> p.toString().endsWith(".java")).sorted().toList()) { + String text = Files.readString(source); + if (!text.contains("@AnalyzeClasses")) { + continue; + } + Matcher field = ARCH_TEST_FIELD.matcher(text); + while (field.find()) { + rules.put(field.group(2), field.group(1).length()); + } + } + } + assertThat(rules) + .as("the scan must find the architecture rules, or both checks pass vacuously") + .isNotEmpty(); + return rules; + } + + private static Path repositoryRoot() { + Path candidate = Path.of("").toAbsolutePath(); + while (candidate != null + && !Files.isRegularFile(candidate.resolve("src/config/architecture/modules.json"))) { + candidate = candidate.getParent(); + } + if (candidate == null) { + throw new IllegalStateException("could not locate the repository root from the test cwd"); + } + return candidate; + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java index 14a2b0b0..e73a6b63 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ArchitectureViolationFixtureTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.Test; * Negative ("violations-as-data") tests proving each architecture rule actually catches the * violation it claims to. Each test loads the intentional fixture classes under {@code * dev.caskeleton.bootstrap.architecture.violations.*} and asserts that the corresponding rule from - * {@link CleanArchitectureTest} reports at least one violation. + * the architecture suite that owns it reports at least one violation. * *

Without this layer, a rule that silently no-matches in production (e.g. a predicate * referencing a package nothing in the codebase happens to populate) would pass vacuously — exactly @@ -158,7 +158,7 @@ class ArchitectureViolationFixtureTest { @Test void noRefreshScopeAnywhereCatchesRefreshScopeAnnotation() { EvaluationResult result = - CleanArchitectureTest.NO_REFRESH_SCOPE_ANYWHERE.evaluate(REFRESH_SCOPE_FIXTURE_ONLY); + ForbiddenApiArchitectureTest.NO_REFRESH_SCOPE_ANYWHERE.evaluate(REFRESH_SCOPE_FIXTURE_ONLY); assertThat(result.hasViolation()) .as("NO_REFRESH_SCOPE_ANYWHERE must catch RefreshScopeUsingFixture (D3/D10)") @@ -167,7 +167,8 @@ class ArchitectureViolationFixtureTest { @Test void domainIsPureCatchesSpringDependencyInDomainPackage() { - EvaluationResult result = CleanArchitectureTest.DOMAIN_IS_PURE.evaluate(VIOLATION_CLASSES); + EvaluationResult result = + DomainPurityArchitectureTest.DOMAIN_IS_PURE.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("DOMAIN_IS_PURE must catch SpringDependentDomainFixture") @@ -177,8 +178,8 @@ class ArchitectureViolationFixtureTest { @Test void applicationDoesNotUseSpringTransactionalAnnotationCatchesViolation() { EvaluationResult result = - CleanArchitectureTest.APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION.evaluate( - VIOLATION_CLASSES); + ApplicationBoundaryArchitectureTest.APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION + .evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -190,7 +191,7 @@ class ArchitectureViolationFixtureTest { @Test void applicationHasNoDiagnosticFrameworkCatchesSlf4jDependency() { EvaluationResult result = - CleanArchitectureTest.APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK.evaluate( + ApplicationBoundaryArchitectureTest.APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK.evaluate( APPLICATION_DIAGNOSTIC_FRAMEWORK_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -203,8 +204,8 @@ class ArchitectureViolationFixtureTest { @Test void applicationDoesNotDependOnApplicationContextCatchesViolation() { EvaluationResult result = - CleanArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT.evaluate( - VIOLATION_CLASSES); + ApplicationBoundaryArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT + .evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -216,7 +217,7 @@ class ArchitectureViolationFixtureTest { @Test void inboundPortImplementationsEndWithUseCaseCatchesBadlyNamedImpl() { EvaluationResult result = - CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE.evaluate( + ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE.evaluate( VIOLATION_CLASSES); assertThat(result.hasViolation()) @@ -227,8 +228,8 @@ class ArchitectureViolationFixtureTest { @Test void inboundPortImplementationsDeclareCapabilityCatchesMissingAnnotation() { EvaluationResult result = - CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY.evaluate( - VIOLATION_CLASSES); + ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY + .evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -244,8 +245,8 @@ class ArchitectureViolationFixtureTest { @Test void readOnlyUseCasesDoNotCallRepositoryWriteMethodsCatchesReadToWriteUpgrade() { EvaluationResult result = - CleanArchitectureTest.READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS.evaluate( - READ_ONLY_REPOSITORY_WRITE_FIXTURE_ONLY); + ApplicationBoundaryArchitectureTest.READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS + .evaluate(READ_ONLY_REPOSITORY_WRITE_FIXTURE_ONLY); assertThat(result.hasViolation()) .as( @@ -257,8 +258,8 @@ class ArchitectureViolationFixtureTest { @Test void bulkWriteCapabilityRequiresWriteRepositoryAccessCatchesReadAccessBulk() { EvaluationResult result = - CleanArchitectureTest.BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS.evaluate( - BULK_WRITE_WITHOUT_WRITE_ACCESS_FIXTURE_ONLY); + ApplicationBoundaryArchitectureTest.BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS + .evaluate(BULK_WRITE_WITHOUT_WRITE_ACCESS_FIXTURE_ONLY); assertThat(result.hasViolation()) .as( @@ -271,8 +272,8 @@ class ArchitectureViolationFixtureTest { @Test void useCaseCapabilityMatchesTransactionPortBoundaryCatchesMissingWriteBoundary() { EvaluationResult result = - CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY.evaluate( - MISSING_TRANSACTION_BOUNDARY_FIXTURE_ONLY); + ApplicationBoundaryArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY + .evaluate(MISSING_TRANSACTION_BOUNDARY_FIXTURE_ONLY); assertThat(result.hasViolation()) .as( @@ -285,8 +286,8 @@ class ArchitectureViolationFixtureTest { @Test void useCaseCapabilityMatchesTransactionPortBoundaryAllowsRootWriteBoundary() { EvaluationResult result = - CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY.evaluate( - ROOT_WRITE_TRANSACTION_BOUNDARY_FIXTURE_ONLY); + ApplicationBoundaryArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY + .evaluate(ROOT_WRITE_TRANSACTION_BOUNDARY_FIXTURE_ONLY); assertThat(result.hasViolation()) .as( @@ -298,8 +299,8 @@ class ArchitectureViolationFixtureTest { @Test void sharedContractScopeRuleCatchesDomainSpecificSharedPackage() { EvaluationResult result = - CleanArchitectureTest.SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES.evaluate( - VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES + .evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -311,7 +312,8 @@ class ArchitectureViolationFixtureTest { @Test void externalOutboundCallsRequireExternalOutboundAllowedCapabilityCatchesUnpermittedCall() { EvaluationResult result = - CleanArchitectureTest.EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY + ApplicationBoundaryArchitectureTest + .EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY .evaluate(OUTBOUND_WITHOUT_PERMISSION_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -325,7 +327,7 @@ class ArchitectureViolationFixtureTest { @Test void requestDtosDoNotSilenceUnknownFieldsCatchesClassLevelIgnoreUnknown() { EvaluationResult result = - CleanArchitectureTest.REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS.evaluate( + TypeBoundaryArchitectureTest.REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS.evaluate( VIOLATION_CLASSES); assertThat(result.hasViolation()) @@ -338,7 +340,7 @@ class ArchitectureViolationFixtureTest { @Test void noJacksonLaissezFaireSubtypeValidatorCatchesUnsafeDefaultTyping() { EvaluationResult result = - CleanArchitectureTest.NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR.evaluate( + ForbiddenApiArchitectureTest.NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR.evaluate( VIOLATION_CLASSES); assertThat(result.hasViolation()) @@ -349,7 +351,8 @@ class ArchitectureViolationFixtureTest { @Test void noJacksonEnableDefaultTypingCallCatchesActivateCall() { EvaluationResult result = - CleanArchitectureTest.NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL.evaluate( + VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -361,7 +364,7 @@ class ArchitectureViolationFixtureTest { @Test void noInheritableThreadLocalCatchesInheritableThreadLocalField() { EvaluationResult result = - CleanArchitectureTest.NO_INHERITABLE_THREAD_LOCAL.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.NO_INHERITABLE_THREAD_LOCAL.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("NO_INHERITABLE_THREAD_LOCAL must catch " + "InheritableThreadLocalFixture (B6)") @@ -371,7 +374,7 @@ class ArchitectureViolationFixtureTest { @Test void controllersDoNotReturnDomainOrEntityTypesCatchesDomainReturningFixture() { EvaluationResult result = - CleanArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES.evaluate( + TypeBoundaryArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES.evaluate( VIOLATION_CLASSES); assertThat(result.hasViolation()) @@ -384,7 +387,7 @@ class ArchitectureViolationFixtureTest { @Test void applicationMethodsDoNotAcceptWebDtosCatchesWebDtoAcceptingFixture() { EvaluationResult result = - CleanArchitectureTest.APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS.evaluate( + ApplicationBoundaryArchitectureTest.APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS.evaluate( VIOLATION_CLASSES); assertThat(result.hasViolation()) @@ -397,7 +400,7 @@ class ArchitectureViolationFixtureTest { @Test void noProblemDetailUsageCatchesProblemDetailFixture() { EvaluationResult result = - CleanArchitectureTest.NO_PROBLEM_DETAIL_USAGE.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.NO_PROBLEM_DETAIL_USAGE.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("NO_PROBLEM_DETAIL_USAGE must catch " + "ProblemDetailUsingFixture (D5)") @@ -407,7 +410,8 @@ class ArchitectureViolationFixtureTest { @Test void noMergePatchJsonMediaTypeCatchesMergePatchFixture() { EvaluationResult result = - CleanArchitectureTest.NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING.evaluate( + VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING must catch " + "MergePatchJsonFixture (B2)") @@ -417,8 +421,8 @@ class ArchitectureViolationFixtureTest { @Test void outboundAdapterMethodReturnsOnlyDomainCatchesRawLeakingFixture() { EvaluationResult result = - CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate( - VIOLATION_CLASSES); + TypeBoundaryArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES + .evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -430,8 +434,8 @@ class ArchitectureViolationFixtureTest { @Test void outboundAdapterMethodRuleCannotBeBypassedWithSettingsSuffix() { EvaluationResult result = - CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate( - B7_SETTINGS_SUFFIX_BYPASS_ONLY); + TypeBoundaryArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES + .evaluate(B7_SETTINGS_SUFFIX_BYPASS_ONLY); assertThat(result.hasViolation()) .as("B7 must catch a raw adapter return even when the owner ends with Settings") @@ -441,8 +445,8 @@ class ArchitectureViolationFixtureTest { @Test void outboundAdapterMethodRuleCannotBeBypassedWithActivationPackage() { EvaluationResult result = - CleanArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES.evaluate( - B7_ACTIVATION_PACKAGE_BYPASS_ONLY); + TypeBoundaryArchitectureTest.OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES + .evaluate(B7_ACTIVATION_PACKAGE_BYPASS_ONLY); assertThat(result.hasViolation()) .as( @@ -454,7 +458,7 @@ class ArchitectureViolationFixtureTest { @Test void outboundAdapterMethodReturnsOnlyDomainCatchesGenericAdapterLeakInIsolation() { EvaluationResult result = - CleanArchitectureTest + ForbiddenApiArchitectureTest .MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS .evaluate(GENERIC_ADAPTER_LEAK_ONLY); @@ -468,7 +472,8 @@ class ArchitectureViolationFixtureTest { @Test void controllerRequestMappingsFollowAip122CatchesKebabPathFixture() { EvaluationResult result = - CleanArchitectureTest.CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122.evaluate( + VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -480,7 +485,7 @@ class ArchitectureViolationFixtureTest { @Test void validCascadeDepthCatchesDeepCascadeFixture() { EvaluationResult result = - CleanArchitectureTest.VALID_CASCADE_DEPTH_AT_MOST_THREE.evaluate(VIOLATION_CLASSES); + TypeBoundaryArchitectureTest.VALID_CASCADE_DEPTH_AT_MOST_THREE.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("VALID_CASCADE_DEPTH_AT_MOST_THREE must catch " + "DeepCascadeRequestFixture (B4)") @@ -493,7 +498,7 @@ class ArchitectureViolationFixtureTest { void validationConstraintsStayAtWebBoundaryCatchesJakartaValidationInDomain() { // Isolated corpus: proves the "..domain.." glob fires on its own. EvaluationResult result = - CleanArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate( + TypeBoundaryArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate( VALIDATION_IN_DOMAIN_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -508,7 +513,7 @@ class ArchitectureViolationFixtureTest { // Isolated corpus: proves the "..application.." glob fires on its own — not vacuously // via the domain fixture also present in the shared violations tree. EvaluationResult result = - CleanArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate( + TypeBoundaryArchitectureTest.VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY.evaluate( VALIDATION_IN_APPLICATION_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -522,7 +527,8 @@ class ArchitectureViolationFixtureTest { @Test void noSseEmitterCatchesSseEmitterFixture() { - EvaluationResult result = CleanArchitectureTest.NO_SSE_EMITTER.evaluate(VIOLATION_CLASSES); + EvaluationResult result = + ForbiddenApiArchitectureTest.NO_SSE_EMITTER.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("NO_SSE_EMITTER must catch SseEmitterUsingFixture (D3)") @@ -532,7 +538,7 @@ class ArchitectureViolationFixtureTest { @Test void noResponseBodyEmitterCatchesResponseBodyEmitterFixture() { EvaluationResult result = - CleanArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("NO_RESPONSE_BODY_EMITTER must catch ResponseBodyEmitterUsingFixture (D3)") @@ -543,7 +549,7 @@ class ArchitectureViolationFixtureTest { void noWebsocketHandlerCatchesSpringWebsocketFixture() { // Isolated corpus: proves the "org.springframework.web.socket.." glob fires on its own. EvaluationResult result = - CleanArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(SPRING_WEBSOCKET_FIXTURE_ONLY); + ForbiddenApiArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(SPRING_WEBSOCKET_FIXTURE_ONLY); assertThat(result.hasViolation()) .as("NO_WEBSOCKET_HANDLER must catch SpringWebSocketHandlerFixture (D3)") @@ -555,7 +561,7 @@ class ArchitectureViolationFixtureTest { // Isolated corpus: proves the "jakarta.websocket.." glob fires on its own — not // vacuously via the spring fixture also present in the shared violations tree. EvaluationResult result = - CleanArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(JAKARTA_WEBSOCKET_FIXTURE_ONLY); + ForbiddenApiArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(JAKARTA_WEBSOCKET_FIXTURE_ONLY); assertThat(result.hasViolation()) .as("NO_WEBSOCKET_HANDLER must catch JakartaWebSocketEndpointFixture (D3)") @@ -567,7 +573,7 @@ class ArchitectureViolationFixtureTest { @Test void noSseEmitterDoesNotCatchStreamingResponseBody() { EvaluationResult result = - CleanArchitectureTest.NO_SSE_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES); + ForbiddenApiArchitectureTest.NO_SSE_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES); assertThat(result.hasViolation()) .as( @@ -580,7 +586,7 @@ class ArchitectureViolationFixtureTest { @Test void noResponseBodyEmitterDoesNotCatchStreamingResponseBody() { EvaluationResult result = - CleanArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES); + ForbiddenApiArchitectureTest.NO_RESPONSE_BODY_EMITTER.evaluate(ALLOWED_STREAMING_CLASSES); assertThat(result.hasViolation()) .as( @@ -593,7 +599,7 @@ class ArchitectureViolationFixtureTest { @Test void noWebsocketHandlerDoesNotCatchStreamingResponseBody() { EvaluationResult result = - CleanArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(ALLOWED_STREAMING_CLASSES); + ForbiddenApiArchitectureTest.NO_WEBSOCKET_HANDLER.evaluate(ALLOWED_STREAMING_CLASSES); assertThat(result.hasViolation()) .as( @@ -609,7 +615,7 @@ class ArchitectureViolationFixtureTest { @Test void noBigdecimalDoubleConstructorCatchesDoubleAndFloatConstructors() { EvaluationResult result = - CleanArchitectureTest.NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR.evaluate(VIOLATION_CLASSES); + ForbiddenApiArchitectureTest.NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -623,7 +629,7 @@ class ArchitectureViolationFixtureTest { @Test void queryPortsPurityRuleCatchesRawDomainReturn() { EvaluationResult result = - CleanArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate( + TypeBoundaryArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate( RAW_LEAK_QUERY_PORT_ONLY); assertThat(result.hasViolation()) @@ -638,7 +644,7 @@ class ArchitectureViolationFixtureTest { // Proves the rule inspects generic type arguments (List) — a // raw-return-type check would pass this vacuously. EvaluationResult result = - CleanArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate( + TypeBoundaryArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate( GENERIC_LEAK_QUERY_PORT_ONLY); assertThat(result.hasViolation()) @@ -651,7 +657,7 @@ class ArchitectureViolationFixtureTest { @Test void queryPortsPurityRuleDoesNotFlagCleanProjectionPort() { EvaluationResult result = - CleanArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate( + TypeBoundaryArchitectureTest.QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES.evaluate( CLEAN_PROJECTION_QUERY_PORT_ONLY); assertThat(result.hasViolation()) @@ -666,7 +672,7 @@ class ArchitectureViolationFixtureTest { @Test void domainHasNoLoggerCatchesLoggerInDomainPackage() { EvaluationResult result = - CleanArchitectureTest.DOMAIN_HAS_NO_LOGGER.evaluate(VIOLATION_CLASSES); + DomainPurityArchitectureTest.DOMAIN_HAS_NO_LOGGER.evaluate(VIOLATION_CLASSES); assertThat(result.hasViolation()) .as("DOMAIN_HAS_NO_LOGGER must catch LoggerUsingDomainFixture (D3)") @@ -677,7 +683,7 @@ class ArchitectureViolationFixtureTest { void valueObjectRuleCatchesAnnotatedPublicNoArgConstructor() { // Isolated corpus: proves the @ValueObject annotation branch fires on its own. EvaluationResult result = - CleanArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate( + DomainPurityArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate( VO_ANNOTATION_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -692,7 +698,7 @@ class ArchitectureViolationFixtureTest { // Isolated corpus: proves the ..domain.vo.. package branch fires on its own — not // vacuously via the annotation fixture also present in the shared violations tree. EvaluationResult result = - CleanArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate( + DomainPurityArchitectureTest.VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR.evaluate( VO_PACKAGE_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -705,7 +711,8 @@ class ArchitectureViolationFixtureTest { @Test void aggregateRootSettersAreNotPublicCatchesPublicSetter() { EvaluationResult result = - CleanArchitectureTest.AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC.evaluate(VIOLATION_CLASSES); + DomainPurityArchitectureTest.AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC.evaluate( + VIOLATION_CLASSES); assertThat(result.hasViolation()) .as( @@ -717,7 +724,7 @@ class ArchitectureViolationFixtureTest { @Test void domainEventsAreRecordsCatchesNonRecordEvent() { EvaluationResult result = - CleanArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate( + DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate( NON_RECORD_DOMAIN_EVENT_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -729,7 +736,8 @@ class ArchitectureViolationFixtureTest { void domainEventsAreRecordsDoesNotFlagARecordEvent() { // Over-block guard: a @DomainEvent that IS a record must NOT be flagged. EvaluationResult result = - CleanArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate(KAFKA_DOMAIN_EVENT_FIXTURE_ONLY); + DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_RECORDS.evaluate( + KAFKA_DOMAIN_EVENT_FIXTURE_ONLY); assertThat(result.hasViolation()) .as("DOMAIN_EVENTS_ARE_RECORDS must NOT flag KafkaDomainEventFixture — it is a record") @@ -740,7 +748,7 @@ class ArchitectureViolationFixtureTest { void domainEventsAreTransportFreeCatchesKafkaType() { // Isolated corpus: proves the org.apache.kafka.. glob fires on its own. EvaluationResult result = - CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( + DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( KAFKA_DOMAIN_EVENT_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -752,7 +760,7 @@ class ArchitectureViolationFixtureTest { void domainEventsAreTransportFreeCatchesSpringHttpType() { // Isolated corpus: proves the org.springframework.http.. glob fires on its own. EvaluationResult result = - CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( + DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( SPRING_HTTP_DOMAIN_EVENT_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -765,7 +773,7 @@ class ArchitectureViolationFixtureTest { // Isolated corpus: proves the jakarta.ws.rs.. glob fires on its own — not vacuously // via the kafka or spring fixtures in the shared tree. EvaluationResult result = - CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( + DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( JAXRS_DOMAIN_EVENT_FIXTURE_ONLY); assertThat(result.hasViolation()) @@ -801,7 +809,7 @@ class ArchitectureViolationFixtureTest { @Test void capabilityRuleCatchesAnOrchestrationThatNeverImplementsTheMarker() { EvaluationResult result = - CleanArchitectureTest.ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY.evaluate( + ApplicationBoundaryArchitectureTest.ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY.evaluate( UNMARKED_ORCHESTRATION_ONLY); assertThat(result.hasViolation()) @@ -814,7 +822,7 @@ class ArchitectureViolationFixtureTest { @Test void portRuleCatchesAnInboundPortCarryingFourOperations() { EvaluationResult result = - CleanArchitectureTest.USE_CASE_PORTS_CARRY_ONE_OPERATION.evaluate( + ApplicationBoundaryArchitectureTest.USE_CASE_PORTS_CARRY_ONE_OPERATION.evaluate( MULTI_OPERATION_PORT_ONLY); assertThat(result.hasViolation()) @@ -825,8 +833,8 @@ class ArchitectureViolationFixtureTest { @Test void controllerRuleCatchesAControllerOutsideAControllerPackage() { EvaluationResult result = - CleanArchitectureTest.CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES.evaluate( - MISFILED_CONTROLLER_ONLY); + AdapterBoundaryArchitectureTest.CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES + .evaluate(MISFILED_CONTROLLER_ONLY); assertThat(result.hasViolation()) .as( @@ -838,8 +846,8 @@ class ArchitectureViolationFixtureTest { @Test void entityRuleCatchesAnOrmMappedTypeOutsideAPersistenceAdapter() { EvaluationResult result = - CleanArchitectureTest.PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER.evaluate( - MISFILED_ENTITY_ONLY); + TypeBoundaryArchitectureTest.PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER + .evaluate(MISFILED_ENTITY_ONLY); assertThat(result.hasViolation()) .as("what makes a class an entity is the mapping annotation, not the directory") @@ -849,8 +857,8 @@ class ArchitectureViolationFixtureTest { @Test void mapperRuleCatchesAMapperOutsideAMapperPackage() { EvaluationResult result = - CleanArchitectureTest.MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES.evaluate( - MISFILED_PERSISTENCE_MAPPER_ONLY); + TypeBoundaryArchitectureTest.MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES + .evaluate(MISFILED_PERSISTENCE_MAPPER_ONLY); assertThat(result.hasViolation()) .as("NotificationRecordMapper sits beside its package, not inside ..mapper..") @@ -861,7 +869,7 @@ class ArchitectureViolationFixtureTest { void domainEventsAreTransportFreeDoesNotFlagACleanEvent() { // Over-block guard: a transport-free @DomainEvent must NOT be flagged. EvaluationResult result = - CleanArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( + DomainPurityArchitectureTest.DOMAIN_EVENTS_ARE_TRANSPORT_FREE.evaluate( NON_RECORD_DOMAIN_EVENT_FIXTURE_ONLY); assertThat(result.hasViolation()) diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java new file mode 100644 index 00000000..5c202774 --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureRuleSupport.java @@ -0,0 +1,70 @@ +package dev.caskeleton.bootstrap.architecture; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; + +/** + * Predicates and matchers used by more than one of the split architecture suites. + * + *

Only what is genuinely shared lives here — two members out of forty-nine. Everything else + * moved to the one suite that uses it, because a support class that collects helpers by being + * convenient is how the class this split undid got large in the first place (BOOT-012). + * + *

Package-private rather than private: the suites are siblings in this package, and the members + * were private only because they used to have no siblings. + */ +final class CleanArchitectureRuleSupport { + + private CleanArchitectureRuleSupport() {} + + /** + * A controller is a class Spring routes requests to, wherever it lives. + * + *

Selecting by the package string {@code ..adapter.inbound.web..controller..} was selecting a + * naming convention rather than a role. {@code NotificationCallbackMvcController} sits in {@code + * ..web.notification.platform.callback}, so the rules that forbid a controller from touching a + * repository or returning an entity never looked at it — the boundary was unguarded for exactly + * the endpoint an external provider posts to. + */ + static final DescribedPredicate ARE_CONTROLLERS = + new DescribedPredicate<>("annotated as a Spring controller, or filed as one") { + @Override + public boolean test(JavaClass candidate) { + return candidate.isAnnotatedWith("org.springframework.web.bind.annotation.RestController") + || candidate.isAnnotatedWith("org.springframework.stereotype.Controller") + || candidate.isMetaAnnotatedWith( + "org.springframework.web.bind.annotation.RestController") + // The package stays in the predicate as well as the annotation. A class filed under + // ..controller.. is claiming the role whether or not Spring is currently routing to + // it, and dropping the package would narrow a rule this change exists to widen. + || (candidate.getPackageName().contains(".adapter.inbound.web") + && candidate.getPackageName().contains(".controller")); + } + }; + + static boolean matches(Object value, String mediaType) { + if (value == null) { + return false; + } + if (value instanceof String s) { + return s.equals(mediaType); + } + if (value instanceof Object[] arr) { + for (Object e : arr) { + if (matches(e, mediaType)) { + return true; + } + } + } + if (value instanceof Iterable iter) { + for (Object e : iter) { + if (matches(e, mediaType)) { + return true; + } + } + } + // Fallback: rendered representation. Catches Spring annotation values that + // get wrapped in opaque ArchUnit holders whose toString carries the literal. + return value.toString().contains(mediaType); + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java deleted file mode 100644 index 0d42acb9..00000000 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java +++ /dev/null @@ -1,2792 +0,0 @@ -package dev.caskeleton.bootstrap.architecture; - -import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; -import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; -import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; -import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices; - -import com.tngtech.archunit.base.DescribedPredicate; -import com.tngtech.archunit.core.domain.JavaAnnotation; -import com.tngtech.archunit.core.domain.JavaClass; -import com.tngtech.archunit.core.domain.JavaClasses; -import com.tngtech.archunit.core.domain.JavaConstructor; -import com.tngtech.archunit.core.domain.JavaEnumConstant; -import com.tngtech.archunit.core.domain.JavaField; -import com.tngtech.archunit.core.domain.JavaMethod; -import com.tngtech.archunit.core.domain.JavaMethodCall; -import com.tngtech.archunit.core.domain.JavaModifier; -import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.archunit.junit.AnalyzeClasses; -import com.tngtech.archunit.junit.ArchTest; -import com.tngtech.archunit.lang.ArchCondition; -import com.tngtech.archunit.lang.ArchRule; -import com.tngtech.archunit.lang.ConditionEvents; -import com.tngtech.archunit.lang.SimpleConditionEvent; -import dev.caskeleton.application.capability.UseCaseCapability; -import dev.caskeleton.application.security.RequiresPermission; -import dev.caskeleton.application.usecase.CommandUseCase; -import dev.caskeleton.application.usecase.QueryUseCase; -import dev.caskeleton.domain.identifier.ResourceId; -import dev.caskeleton.domain.stereotype.AggregateRoot; -import dev.caskeleton.domain.stereotype.DomainEvent; -import dev.caskeleton.domain.stereotype.ValueObject; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.regex.Pattern; -import org.hibernate.type.SqlTypes; -import org.springframework.boot.health.actuate.endpoint.HealthEndpoint; -import org.springframework.boot.health.contributor.Health; -import org.springframework.boot.health.contributor.HealthIndicator; - -@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class) -class CleanArchitectureTest { - - @ArchTest - static final ArchRule DOMAIN_IS_PURE = - noClasses() - .that() - .resideInAPackage("..domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "org.springframework..", - "jakarta.persistence..", - "javax.persistence..", - "jakarta.servlet..", - "org.hibernate..", - "lombok..", - "..application..", - "..adapter..", - "..bootstrap..", - "..service..", - "..infra..", - "..presentation..", - "..cmd..") - .as( - "domain-core must be framework-neutral POJO — no Spring / JPA / " - + "Hibernate / Lombok generated bytecode " - + "(feature-architecture-enforcement-rules D3)") - .allowEmptyShould(true); - - // ---- feature-domain-modeling-guardrails (modeling-specific rules; owner = this branch) ---- - // Domain framework-neutrality is delegated to DOMAIN_IS_PURE above - // (feature-architecture-enforcement-rules D3). The rules below add the - // modeling-specific guardrails this branch owns: logger ban (D3), value-object - // invariant surface (D5/D6), aggregate mutator visibility (D7), and transport-free - // domain events (D4/D8). They key off the dev.caskeleton.domain.stereotype markers. - - @ArchTest - static final ArchRule DOMAIN_HAS_NO_LOGGER = - noClasses() - .that() - .resideInAPackage("..domain..") - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "org.slf4j..", - "java.util.logging..", - "ch.qos.logback..", - "org.apache.logging.log4j..") - .as( - "D3 DOMAIN_HAS_NO_LOGGER: the domain layer must not depend on a logging " - + "framework (SLF4J / java.util.logging / Logback / Log4j). An invariant " - + "violation is expressed as a domain exception carrying a safe reason " - + "value (noun enum); the application layer translates that into a log " - + "line. This is a ca-tmpl convention (UNSUPPORTED_DECISION — no official " - + "standard source), kept SEPARATE from DOMAIN_IS_PURE to preserve the " - + "owner boundary of feature-architecture-enforcement-rules D3 " - + "(feature-domain-modeling-guardrails D3, §2)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR = - classes() - .that() - .areAnnotatedWith(ValueObject.class) - .or() - .resideInAPackage("..domain.vo..") - .should(notHaveAPublicNoArgConstructor()) - .as( - "D5/D6 VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR: a @ValueObject " - + "(or any type under ..domain.vo..) must validate its invariant on the " - + "sole construction path and must NOT expose a public no-arg constructor " - + "— an empty constructor is an invariant-bypassing back door. Records " - + "with components satisfy this automatically " - + "(feature-domain-modeling-guardrails D5/D6, §3)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC = - methods() - .that() - .haveNameMatching("set.*") - .and() - .areDeclaredInClassesThat() - .areAnnotatedWith(AggregateRoot.class) - .should() - .notBePublic() - .as( - "D7 AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC: a @AggregateRoot mutates its " - + "state only through intention-revealing methods that enforce invariants " - + "— a raw set* mutator must be package-private/protected (the visibility " - + "Vernon Option A ORM-external reconstruction relies on). ArchUnit only " - + "statically reaches the set* surface; other state-changing methods stay " - + "a code-review concern (feature-domain-modeling-guardrails D7, §4)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule DOMAIN_EVENTS_ARE_RECORDS = - classes() - .that() - .areAnnotatedWith(DomainEvent.class) - .should(beRecordTypes()) - .as( - "D4/D8 DOMAIN_EVENTS_ARE_RECORDS: a @DomainEvent is an immutable fact and " - + "must be declared as a record (feature-domain-modeling-guardrails D4/D8, §5)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule DOMAIN_EVENTS_ARE_TRANSPORT_FREE = - noClasses() - .that() - .areAnnotatedWith(DomainEvent.class) - .should() - .dependOnClassesThat() - .resideInAnyPackage("org.apache.kafka..", "org.springframework.http..", "jakarta.ws.rs..") - .as( - "D4/D8 DOMAIN_EVENTS_ARE_TRANSPORT_FREE: a @DomainEvent carries domain data " - + "only and must not depend on a broker/wire/HTTP type (Kafka SDK, Spring " - + "HTTP, JAX-RS). Translating a domain event into an integration/wire event " - + "is the application/infrastructure boundary's job. UNSUPPORTED_IMPL_DECISION: " - + "the forbidden list enumerates only the SDKs ca-tmpl could plausibly add — " - + "extend it when a new broker/transport is introduced " - + "(feature-domain-modeling-guardrails D4/D8, §5)") - .allowEmptyShould(true); - - // ---- feature-persistence-auditing-contract D2 (domain stays free of audit metadata) ---- - // DOMAIN_IS_PURE already bans jakarta.persistence.. / org.springframework.. (incl. - // org.springframework.data..) from ..domain.., so @CreatedDate / @Column / AuditableEntity - // cannot reach the domain. This rule adds the audit-specific guardrail D2 owns: even as - // plain POJO fields, a domain aggregate must carry ZERO audit metadata — who/when - // created/modified is an adapter-persistence concern (AuditableEntity @MappedSuperclass), - // and putting it in the domain would invert the dependency (the domain would know "who - // logged in") and leak the infrastructure concern inward. - @ArchTest - static final ArchRule DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS = - noClasses() - .that() - .resideInAPackage("..domain..") - .should(declareAnAuditField()) - .as( - "D2 DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS: a domain class must not declare " - + "an audit field (createdAt / updatedAt / createdBy / updatedBy) — audit " - + "metadata lives only on the adapter-persistence AuditableEntity " - + "@MappedSuperclass so the domain aggregate carries none of it " - + "(feature-persistence-auditing-contract D2)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT = - noClasses() - .that() - .resideInAPackage("..application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "..adapter..", - "..bootstrap..", - "..infra..", - "..presentation..", - "..cmd..", - "jakarta.persistence..", - "javax.persistence..", - "jakarta.servlet..", - "org.springframework.web..", - "org.hibernate..") - .allowEmptyShould(true); - - /** - * The application never names the messaging platform's types. - * - *

The new platform has its own {@code MessagePublisher}, {@code MessageEnvelope} and {@code - * OutboxStatus}, and the application already has a canonical publish port and outbox model of its - * own. An import from application code would make the platform's vocabulary the business boundary - * — and its {@code OutboxStatus.FAILED} means the opposite of the legacy {@code - * OutboxEventStatus.FAILED}, so the two models cannot be mixed by name without inverting - * retryable and terminal. The translation belongs in an outbound bridge adapter. - */ - @ArchTest - static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM = - noClasses() - .that() - .resideInAPackage("..application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("dev.caskeleton.messaging..") - .because( - "APPLICATION_DOES_NOT_DEPEND_ON_THE_MESSAGING_PLATFORM: the application owns its " - + "publish port and outbox model; a bridge adapter translates, and the two " - + "outbox status models mean opposite things under the same names") - .allowEmptyShould(true); - - /** - * The two notification models must not learn about each other. - * - *

NOTIF-ADR-005 makes the platform canonical and keeps R1's hundred public types in place - * without new consumers. Nothing enforced that: {@code Channel}, plan, dispatch and - * receipt/evidence exist in both namespaces with the same simple names, so a new consumer picks - * one by import order and the two models drift apart while both claim to be current. - * - *

The exception is a single compatibility package. If a bridge is ever needed it goes there, - * and only there — an exception in several places is not an exception, it is the second canonical - * model the ADR exists to prevent. - */ - @ArchTest - static final ArchRule NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.application.notification.platform..") - .and() - .resideOutsideOfPackage("dev.caskeleton.application.notification.compatibility.r1..") - .should() - .dependOnClassesThat( - new com.tngtech.archunit.base.DescribedPredicate<>( - "reside in the R1 notification namespace") { - @Override - public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { - String name = candidate.getPackageName(); - return name.equals("dev.caskeleton.application.notification"); - } - }) - .because( - "NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER: NOTIF-ADR-005 makes the " - + "platform canonical; a bridge belongs in " - + "dev.caskeleton.application.notification.compatibility.r1 and nowhere else") - .allowEmptyShould(true); - - /** - * A transport must depend on an inbound port, never on the class behind it. - * - *

The notification callback controller injected {@code - * IngestProviderCallbackApplicationUseCase} directly. The existing capability rules did not catch - * it, and could not: they inspect types that already implement {@code CommandUseCase} or {@code - * QueryUseCase}, so an orchestration that never adopted the marker is invisible to the very gate - * that would have required it. This rule looks from the other side — at what the transport - * depends on — which is where the omission is visible. - */ - @ArchTest - static final ArchRule INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.adapter.inbound..") - .should() - .dependOnClassesThat( - new com.tngtech.archunit.base.DescribedPredicate<>( - "are concrete application orchestration services") { - @Override - public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { - return candidate.getPackageName().startsWith("dev.caskeleton.application.") - && !candidate.isInterface() - && !candidate - .getModifiers() - .contains(com.tngtech.archunit.core.domain.JavaModifier.ABSTRACT) - && (candidate.getSimpleName().endsWith("Service") - || candidate.getSimpleName().endsWith("Orchestrator")); - } - }) - .because( - "INBOUND_ADAPTERS_DEPEND_ON_PORTS_NOT_APPLICATION_SERVICES: an entrypoint reached " - + "through a concrete class bypasses the use-case fitness gate, which is what " - + "declares transaction mode, repository access and outbound permission") - .allowEmptyShould(true); - - /** - * An orchestration entrypoint, recognised by what it does rather than by what it implements. - * - *

This is the gap NTF-019 names. Every existing capability rule starts from {@code - * .that().implement(CommandUseCase.class)} — so a new orchestration that simply never implements - * the marker is not checked by any of them. The rules were not wrong; they were unreachable. A - * class that opens a transaction and writes through a repository port is an orchestration - * entrypoint whether or not anybody remembered to say so. - */ - private static final DescribedPredicate ARE_ORCHESTRATION_ENTRYPOINTS = - new DescribedPredicate<>("application orchestration entrypoints") { - @Override - public boolean test(JavaClass candidate) { - // ".application." rather than a prefix match, matching the glob convention the rest of - // this file uses — and, concretely, so the negative fixture that proves this rule fires - // is itself inside the rule's scope. A rule whose only corpus is production code cannot - // be shown to fire at all. - if (!candidate.getPackageName().contains(".application.") - || candidate.isInterface() - || candidate.getModifiers().contains(JavaModifier.ABSTRACT) - || candidate.isRecord() - || candidate.isEnum()) { - return false; - } - // Holding a transaction port is the tell. A helper that only computes has no boundary - // to declare; a class that can start a unit of work is deciding its shape. - // - // Read from the field types rather than from the dependency graph: an isolated corpus - // (the negative fixture) resolves the port as a stub, and the dependency edge is then - // absent — which would make this rule pass on the very shape it exists to catch. - return candidate.getFields().stream() - .anyMatch( - field -> - field - .getRawType() - .getFullName() - .equals("dev.caskeleton.application.transaction.TransactionPort")); - } - }; - - @ArchTest - static final ArchRule ORCHESTRATION_ENTRYPOINTS_DECLARE_A_CAPABILITY = - classes() - .that(ARE_ORCHESTRATION_ENTRYPOINTS) - .and() - .haveSimpleNameEndingWith("UseCase") - .should() - .beAnnotatedWith(UseCaseCapability.class) - .as( - "NTF-019: a use case that opens a transaction declares its capability. The existing " - + "rules all start from the marker interface, so an entrypoint that never " - + "implements it is checked by nothing at all") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule OUTBOUND_NOTIFICATION_HOLDS_NO_ADMIN_POLICY = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.adapter.outbound.notification..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName("dev.caskeleton.application.transaction.TransactionPort") - .orShould() - .dependOnClassesThat() - .haveFullyQualifiedName( - "dev.caskeleton.application.notification.platform.admin.AdminActor") - .orShould() - .dependOnClassesThat() - .haveFullyQualifiedName( - "dev.caskeleton.application.notification.platform.admin.NotificationAdminAuthority") - .as( - "NTF-020: who may perform an operator action, and where a unit of work begins, are " - + "decisions about the product. They were both made in the layer whose job is " - + "speaking provider protocols, where no application test could reach them") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_A_CAPABILITY = - classes() - .that( - new DescribedPredicate("implement an inbound port") { - @Override - public boolean test(JavaClass candidate) { - return !candidate.isInterface() - && !candidate.getModifiers().contains(JavaModifier.ABSTRACT) - && candidate.getAllRawInterfaces().stream() - .anyMatch( - implemented -> implemented.getPackageName().contains(".port.in")); - } - }) - .should() - .beAnnotatedWith(UseCaseCapability.class) - .as( - "NTF-019: implementing an inbound port is what makes a class an entrypoint, whatever " - + "it is named. The name-based half of this check would miss a class called " - + "NotificationSubmissionService that a controller reaches directly") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule USE_CASE_PORTS_CARRY_ONE_OPERATION = - classes() - .that() - .resideInAPackage("..application..port.in..") - .and() - .areInterfaces() - .should(declareAtMostOneAbstractOperation()) - .as( - "NTF-019: one operation per inbound port. NotificationOrchestrator mixed submit, " - + "schedule, cancel and get, and no single transaction mode is true of all four " - + "— the compiler agrees, since CommandUseCase cannot be inherited twice with " - + "different type arguments") - .allowEmptyShould(true); - - /** - * Hold an inbound port to a single abstract operation. - * - * @return the condition - */ - private static ArchCondition declareAtMostOneAbstractOperation() { - return new ArchCondition<>("declare at most one abstract operation") { - @Override - public void check(JavaClass item, ConditionEvents events) { - List abstractMethods = - item.getMethods().stream() - .filter(method -> !method.getModifiers().contains(JavaModifier.STATIC)) - .filter(method -> method.getModifiers().contains(JavaModifier.ABSTRACT)) - .toList(); - if (abstractMethods.size() > 1) { - events.add( - SimpleConditionEvent.violated( - item, - item.getName() - + " declares " - + abstractMethods.size() - + " operations; an inbound port declares one, so its capability can be " - + "stated honestly")); - } - } - }; - } - - @ArchTest - static final ArchRule APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "org.slf4j..", - "java.util.logging..", - "ch.qos.logback..", - "org.apache.logging.log4j..", - "io.micrometer..") - .as( - "APPLICATION_HAS_NO_DIAGNOSTIC_FRAMEWORK: application-core owns typed outbound " - + "diagnostic ports, while logging and metrics implementations belong to " - + "outbound adapters") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule SAMPLE_APPLICATION_HAS_NO_SLF4J = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.sample.portfolio.application..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("org.slf4j..") - .as( - "SAMPLE_APPLICATION_HAS_NO_SLF4J: sample application collaborators read " - + "correlation context through application-core CorrelationIdPort; inbound " - + "adapters own MDC") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule SAMPLE_APPLICATION_LEGACY_OBJECT_STORAGE_IMPORT_IS_FROZEN = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.sample.portfolio.application..") - .and() - .doNotHaveFullyQualifiedName( - "dev.caskeleton.sample.portfolio.application.poster.UploadPosterImageUseCase") - .should() - .dependOnClassesThat() - .resideInAPackage("dev.caskeleton.application.storage") - .as( - "Only the characterized UploadPosterImageUseCase may temporarily import the legacy " - + "ObjectStoragePort/StoredObject contract. New sample business code must use " - + "the semantic objectstorage ports. Task 23 moves compatibility into the exact " - + "legacy slice; Task 24 separately reviews the administrative migration seam.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule APPLICATION_DOES_NOT_USE_SPRING_TRANSACTIONAL_ANNOTATION = - noClasses() - .that() - .resideInAPackage("..application..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional") - .as( - "application package must use TransactionPort instead of " - + "@Transactional (feature-application-port-usecase-contract D3)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE = - classes() - .that() - .implement(CommandUseCase.class) - .or() - .implement(QueryUseCase.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should() - .haveSimpleNameEndingWith("UseCase") - .as( - "CommandUseCase / QueryUseCase implementations must end with 'UseCase' " - + "(feature-application-port-usecase-contract D1)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY = - classes() - .that() - .implement(CommandUseCase.class) - .or() - .implement(QueryUseCase.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should() - .beAnnotatedWith(UseCaseCapability.class) - .as( - "every CommandUseCase / QueryUseCase implementation must declare " - + "@UseCaseCapability (feature-application-port-usecase-contract §판정 기준)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule APPLICATION_DOES_NOT_DEPEND_ON_APPLICATION_CONTEXT = - noClasses() - .that() - .resideInAPackage("..application..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName("org.springframework.context.ApplicationContext") - .as( - "application package must not depend on Spring ApplicationContext " - + "(blocks getBean(Class) reflection-style bypass — " - + "feature-architecture-enforcement-rules D11). string-key bean " - + "lookup / Class.forName(String) bypass remains ArchUnit's " - + "static-analysis limit per D12.") - .allowEmptyShould(true); - - // NOTE: the former `inbound_port_implementations_do_not_declare_keyed_idempotency` - // freeze rule (feature-application-port-usecase-contract D14) is removed now that - // feature-rate-limit-idempotency-contract has merged: Idempotency.KEYED is backed - // by IdempotencyExecutor + IdempotencyStorePort + the idempotency_record table, so a - // use case may declare it. - - // ---- feature-repository-access-permission-contract D12 / D6 (repositoryAccess coherence) ---- - - @ArchTest - static final ArchRule READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS = - classes() - .that() - .areAnnotatedWith(UseCaseCapability.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should(notCallRepositoryWriteUnlessWriteRepository()) - .as( - "D12 READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS: a use case " - + "whose @UseCaseCapability(repositoryAccess) is not WRITE_REPOSITORY must " - + "not DIRECTLY call a write method " - + "(save / saveAll / saveAndFlush / delete* / remove* / update / insert / " - + "persist / merge / store) of a *Repository port. Declaring READ_REPOSITORY " - + "(or NONE) then mutating is the silent read-to-write upgrade this contract " - + "blocks (feature-repository-access-permission-contract D12). " - + "UNSUPPORTED_IMPL_DECISION: ArchUnit static analysis only reaches DIRECT " - + "calls — a write reached through a helper / mapper escapes the call graph " - + "and stays a code-review concern (D12 static-analysis limit). The write " - + "method-name allowlist is a ca-tmpl convention; extend it when a new " - + "mutating verb enters the repository vocabulary.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS = - classes() - .that() - .areAnnotatedWith(UseCaseCapability.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should(haveWriteRepositoryAccessWhenBulkWrite()) - .as( - "D6 BULK_WRITE_CAPABILITY_REQUIRES_WRITE_REPOSITORY_ACCESS: " - + "@UseCaseCapability(bulkWrite = true) requires repositoryAccess = " - + "WRITE_REPOSITORY — a bulk write (N > 100) is still a write, and the " - + "capability registry binds BULK_WRITE to WRITE_REPOSITORY " - + "(feature-repository-access-permission-contract D6).") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY = - classes() - .that() - .areAnnotatedWith(UseCaseCapability.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should(callTransactionPortMethodRequiredByCapability()) - .as( - "feature-domain-feature-onboarding-contract D4: a use case that declares a " - + "repository-backed transaction capability must call the matching " - + "TransactionPort boundary directly: READ_REPOSITORY+READ_ONLY -> inRead, " - + "WRITE_REPOSITORY+WRITE -> inWrite or inRootWrite, REQUIRES_NEW -> inNew. " - + "RepositoryAccess.NONE may intentionally skip a DB transaction. " - + "UNSUPPORTED_IMPL_DECISION: static analysis reaches direct calls only; a " - + "transaction hidden behind a helper remains a code-review concern.") - .allowEmptyShould(true); - - // ---- feature-authentication-authorization-contract D1 / D4 (product authorization) ---- - - @ArchTest - static final ArchRule MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION = - classes() - .that() - .areAnnotatedWith(UseCaseCapability.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should(declareRequiredPermissionWhenMutating()) - .as( - "D4 MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION: a use case whose " - + "@UseCaseCapability(repositoryAccess) is WRITE_REPOSITORY MUST also declare " - + "@RequiresPermission, so a mutating operation cannot silently ship without a " - + "permission gate (feature-authentication-authorization-contract D4). The " - + "skeleton baseline is mutating-only; public reads are exempt. " - + "UNSUPPORTED_IMPL_DECISION: this rule checks the type-level annotation (the " - + "@UseCaseCapability convention); a method-only @RequiresPermission on a use " - + "case still trips it. AOP self-invocation / non-bean entry points stay a " - + "static-analysis blind spot (D4 limit).") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY = - noClasses() - .that() - .resideInAnyPackage("..application..", "..domain..") - .should() - .dependOnClassesThat() - .resideInAPackage("org.springframework.security..") - .as( - "D1 APPLICATION_AND_DOMAIN_DO_NOT_DEPEND_ON_SPRING_SECURITY: application-core and " - + "domain-core must stay free of Spring Security types. The authorization " - + "DECISION is a framework-free port (@RequiresPermission / AuthorizationPort / " - + "AuthorizationPrincipal); the Spring-aware enforcement MECHANISM " - + "(AuthorizationManager) lives only in adapter:inbound:web " - + "(feature-authentication-authorization-contract D1).") - .allowEmptyShould(true); - - // ---- feature-management-actuator-security-contract D6 (health shape-ownership guardrail) ---- - // ManagementSecurityConfig (in ..management.security..) must NOT import or depend on - // HealthEndpoint, HealthIndicator, or Health — the health endpoint shape belongs to - // the runtime-health branch. The security config only matches/permits endpoints by id string; - // it never inspects health internals. - - @ArchTest - static final ArchRule MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS = - noClasses() - .that() - .resideInAPackage("..management.security..") - .should() - .dependOnClassesThat() - .areAssignableTo(HealthEndpoint.class) - .orShould() - .dependOnClassesThat() - .areAssignableTo(HealthIndicator.class) - .orShould() - .dependOnClassesThat() - .areAssignableTo(Health.class) - .as( - "D6 MANAGEMENT_SECURITY_DOES_NOT_DEPEND_ON_HEALTH_INTERNALS: classes in " - + "..management.security.. must not import or depend on HealthEndpoint, " - + "HealthIndicator, or Health — the health endpoint shape is owned " - + "by the runtime-health branch. The actuator security config matches " - + "endpoints by id only, never by health internals " - + "(feature-management-actuator-security-contract D6).") - .allowEmptyShould(true); - - private static final Set REPOSITORY_WRITE_METHOD_NAMES = - Set.of( - "save", - "saveAll", - "saveAndFlush", - "delete", - "deleteById", - "deleteAll", - "remove", - "removeAll", - "removeById", - "update", - "insert", - "persist", - "merge", - "store"); - - private static ArchCondition notCallRepositoryWriteUnlessWriteRepository() { - return new ArchCondition<>( - "not call a *Repository write method unless " + "repositoryAccess = WRITE_REPOSITORY") { - @Override - public void check(JavaClass item, ConditionEvents events) { - JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); - Object repositoryAccess = annotation.get("repositoryAccess").orElse(null); - boolean writeRepository = - repositoryAccess instanceof JavaEnumConstant constant - && "WRITE_REPOSITORY".equals(constant.name()); - if (writeRepository) { - return; // WRITE_REPOSITORY use cases are permitted to mutate. - } - for (JavaMethodCall call : item.getMethodCallsFromSelf()) { - String targetOwner = call.getTargetOwner().getSimpleName(); - String targetName = call.getName(); - if (targetOwner.endsWith("Repository") - && REPOSITORY_WRITE_METHOD_NAMES.contains(targetName)) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " declares repositoryAccess != " - + "WRITE_REPOSITORY but directly calls " - + targetOwner - + "." - + targetName - + "(...) — a repository write " - + "(feature-repository-access-permission-contract D12)")); - } - } - } - }; - } - - private static ArchCondition declareRequiredPermissionWhenMutating() { - return new ArchCondition<>( - "declare @RequiresPermission when repositoryAccess = WRITE_REPOSITORY") { - @Override - public void check(JavaClass item, ConditionEvents events) { - JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); - Object repositoryAccess = annotation.get("repositoryAccess").orElse(null); - boolean writeRepository = - repositoryAccess instanceof JavaEnumConstant constant - && "WRITE_REPOSITORY".equals(constant.name()); - if (!writeRepository) { - return; - } - if (!item.isAnnotatedWith(RequiresPermission.class.getName())) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " declares @UseCaseCapability(repositoryAccess = " - + "WRITE_REPOSITORY) but is missing @RequiresPermission — a mutating use " - + "case must declare the permission it requires " - + "(feature-authentication-authorization-contract D4)")); - } - } - }; - } - - private static ArchCondition haveWriteRepositoryAccessWhenBulkWrite() { - return new ArchCondition<>( - "declare repositoryAccess = WRITE_REPOSITORY when bulkWrite = true") { - @Override - public void check(JavaClass item, ConditionEvents events) { - JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); - boolean bulkWrite = Boolean.TRUE.equals(annotation.get("bulkWrite").orElse(false)); - if (!bulkWrite) { - return; - } - Object repositoryAccess = annotation.get("repositoryAccess").orElse(null); - boolean writeRepository = - repositoryAccess instanceof JavaEnumConstant constant - && "WRITE_REPOSITORY".equals(constant.name()); - if (!writeRepository) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " declares @UseCaseCapability(bulkWrite = true) " - + "without repositoryAccess = WRITE_REPOSITORY " - + "(feature-repository-access-permission-contract D6)")); - } - } - }; - } - - @SuppressWarnings("resource") - private static ArchCondition callTransactionPortMethodRequiredByCapability() { - return new ArchCondition<>("call the TransactionPort method required by @UseCaseCapability") { - @Override - public void check(JavaClass item, ConditionEvents events) { - JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); - String transactionMode = enumAnnotationValue(annotation, "transactionMode"); - String repositoryAccess = enumAnnotationValue(annotation, "repositoryAccess"); - - Set requiredMethods = Set.of(); - if ("REQUIRES_NEW".equals(transactionMode)) { - requiredMethods = Set.of("inNew"); - } else if ("WRITE".equals(transactionMode) && "WRITE_REPOSITORY".equals(repositoryAccess)) { - requiredMethods = Set.of("inWrite", "inRootWrite"); - } else if ("READ_ONLY".equals(transactionMode) - && "READ_REPOSITORY".equals(repositoryAccess)) { - requiredMethods = Set.of("inRead"); - } - - if (requiredMethods.isEmpty()) { - return; - } - - for (JavaMethodCall call : item.getMethodCallsFromSelf()) { - if ("dev.caskeleton.application.transaction.TransactionPort" - .equals(call.getTargetOwner().getFullName()) - && requiredMethods.contains(call.getName())) { - return; - } - } - - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " declares @UseCaseCapability(transactionMode = " - + transactionMode - + ", repositoryAccess = " - + repositoryAccess - + ") but does not directly call one of TransactionPort." - + requiredMethods - + "(...)")); - } - }; - } - - private static String enumAnnotationValue(JavaAnnotation annotation, String property) { - Object value = annotation.get(property).orElse(null); - return value instanceof JavaEnumConstant constant ? constant.name() : ""; - } - - private static ArchCondition resideInAllowedSharedContractPackage() { - return new ArchCondition<>("reside in an allowed shared operational contract package") { - private final Set allowedPackages = - Set.of( - "dev.caskeleton.shared", - "dev.caskeleton.shared.response", - "dev.caskeleton.shared.request", - "dev.caskeleton.shared.error", - "dev.caskeleton.shared.operation", - "dev.caskeleton.shared.headers", - "dev.caskeleton.shared.logging", - "dev.caskeleton.shared.tracing", - "dev.caskeleton.shared.metrics", - "dev.caskeleton.shared.registry", - "dev.caskeleton.shared.annotation", - // Not this module at all — see the defect note on the rule above. The Mongo leaf has - // a - // tenancy.shared package of its own and only began tripping this rule when that leaf - // joined the composition root's analysis scope. - "dev.caskeleton.adapter.outbound.mongo.advanced.tenancy.shared", - // The five master switches and their strict parse. Operational rather than domain: - // it names environment keys and rejects unreadable values, and it lives here because - // the environment post-processor needs it before a Spring context exists, so it must - // not depend on Spring. - "dev.caskeleton.shared.activation", - // feature-authentication-authorization-contract: the Permission - // (resource:action) value object is a Java-only operational - // authorization contract consumed by AuthorizationPort. - "dev.caskeleton.shared.security", - // feature-runtime-context-propagation-contract: the domain-context - // propagation port + default ThreadLocal strategy + factory - // (swappable abstraction, the rate-limit pattern). - "dev.caskeleton.shared.concurrency"); - - @Override - public void check(JavaClass item, ConditionEvents events) { - String packageName = item.getPackageName(); - boolean allowed = false; - for (String allowedPackage : allowedPackages) { - if (packageName.equals(allowedPackage) || packageName.startsWith(allowedPackage + ".")) { - allowed = true; - break; - } - } - if (!allowed) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " resides in " - + packageName - + ", which is not an allowed shared operational contract package")); - } - } - }; - } - - // ---- feature-repository-access-permission-contract D7 (outbound capability coherence) ---- - - /** - * Outbound ports are the interfaces a use case must hold {@code externalOutboundAllowed} to call. - * They are identified by their adapter binding — any interface implemented by a production class - * residing under {@code ..adapter.outbound..} (e.g. RepoStatsPort ← RepoStatsPortClient), - * EXCLUDING the {@code ..adapter.outbound.identifier..} sub-package: UUIDv7 id/event-id factories - * are a non-IO driven adapter (in-process generation/codec, no external HTTP / message / cache - * call — see IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP), so calling one - * directly does not require {@code externalOutboundAllowed = true}. Also EXCLUDING the {@code - * ..adapter.outbound.persistence..} sub-package: repositories/stores are internal (driven) - * outbound adapters — a use case calling a repository does not require {@code - * externalOutboundAllowed = true}, and their package moved under {@code adapter.outbound} only as - * a topology rename (Task 7 owns the holistic external-vs-internal split). Precomputed from - * production classes (tests excluded) so the coherence rule has no application-side marker - * dependency. - */ - private static final Set OUTBOUND_PORT_NAMES = outboundPortNames(); - - private static Set outboundPortNames() { - JavaClasses production = - new ClassFileImporter() - .withImportOption(new ProductionClassImportOption()) - .importPackages("dev.caskeleton"); - Set names = new HashSet<>(); - for (JavaClass type : production) { - if (type.getPackageName().contains(".adapter.outbound.") - && !isInProcessDrivenOutbound(type.getPackageName())) { - for (JavaClass iface : type.getAllRawInterfaces()) { - names.add(iface.getFullName()); - } - } - } - return names; - } - - /** - * True when {@code packageName} is an IN-PROCESS (non-IO) driven outbound concern — {@code - * ..adapter.outbound.persistence..} (repositories / stores) or {@code - * ..adapter.outbound.identifier..} (UUIDv7 id/event-id generation & codec). Both nest under - * {@code ..adapter.outbound..} as a Task-1–6 topology rename but perform NO external IO (no HTTP - * / message / cache network call), so an interface they implement is NOT an external outbound - * port and a use case calling one directly does not require {@code externalOutboundAllowed = - * true}. This single predicate consolidates the two per-package {@code .contains(...)} exclusions - * that Tasks 2/6 added locally to {@link #outboundPortNames()}. - * - *

The genuinely external-IO outbound adapters — {@code httpclient}, {@code messaging}, {@code - * notification}, and {@code cache} — are deliberately NOT treated as in-process here: a Redis - * cache call is a real network dependency, so the D7 external-outbound gate must keep covering - * it. Folding cache/support into this exclusion (as an earlier plan sketch suggested) would - * weaken that gate for no current benefit and contradict this heuristic's own IO-vs-in- process - * rationale, so it is intentionally NOT done. - */ - private static boolean isInProcessDrivenOutbound(String packageName) { - return packageName.contains(".adapter.outbound.persistence") - || packageName.contains(".adapter.outbound.identifier"); - } - - @ArchTest - static final ArchRule EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY = - classes() - .that() - .areAnnotatedWith(UseCaseCapability.class) - .and() - .areNotInterfaces() - .and() - .areNotAnonymousClasses() - .and() - .areNotMemberClasses() - .should(notCallOutboundPortUnlessAllowed()) - .as( - "D7 EXTERNAL_OUTBOUND_CALLS_REQUIRE_EXTERNAL_OUTBOUND_ALLOWED_CAPABILITY: a " - + "use case that DIRECTLY calls an outbound port (an interface implemented " - + "by a production class under ..adapter.outbound..) must declare " - + "@UseCaseCapability(externalOutboundAllowed = true). Without it, emitting " - + "an external HTTP / message / cache call is forbidden " - + "(feature-repository-access-permission-contract D7). Outbound ports are " - + "identified by their adapter binding (RepoStatsPort ← RepoStatsPortClient), " - + "so no application-side marker is required. UNSUPPORTED_IMPL_DECISION: " - + "ArchUnit reaches DIRECT calls only — a call routed through a helper / " - + "mapper escapes the call graph and stays a code-review concern " - + "(static-analysis limit, mirrors D12).") - .allowEmptyShould(true); - - private static ArchCondition notCallOutboundPortUnlessAllowed() { - return new ArchCondition<>("not call an outbound port unless externalOutboundAllowed = true") { - @Override - public void check(JavaClass item, ConditionEvents events) { - JavaAnnotation annotation = item.getAnnotationOfType(UseCaseCapability.class.getName()); - boolean allowed = - Boolean.TRUE.equals(annotation.get("externalOutboundAllowed").orElse(false)); - if (allowed) { - return; // externalOutboundAllowed use cases may emit outbound calls. - } - for (JavaMethodCall call : item.getMethodCallsFromSelf()) { - String targetOwner = call.getTargetOwner().getFullName(); - if (OUTBOUND_PORT_NAMES.contains(targetOwner)) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " declares externalOutboundAllowed = " - + "false but directly calls outbound port " - + targetOwner - + " (feature-repository-access-permission-contract D7)")); - } - } - } - }; - } - - @ArchTest - static final ArchRule WEB_ADAPTER_DOES_NOT_DEPEND_ON_PERSISTENCE_OR_OUTBOUND_ADAPTERS = - noClasses() - .that() - .resideInAPackage("..adapter.inbound.web..") - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "..adapter.outbound.persistence..", - "..adapter.outbound..", - "..adapter.outbound.identifier..", - "org.springframework.data.repository..", - "org.springframework.data.jpa.repository..", - "jakarta.persistence..", - "javax.persistence..", - "org.hibernate.."); - - @ArchTest - static final ArchRule PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS = - noClasses() - .that() - .resideInAPackage("..adapter.outbound.persistence..") - .should() - // persistence now nests under adapter.outbound (topology rename), so the forbidden - // outbound target must exclude persistence's own package — otherwise the abundant - // intra-persistence references (adapter -> entity/mapper/repository) self-overlap. - // Mirrors the IDENTIFIER_ADAPTER_... predicate-form self-exclusion below. - .dependOnClassesThat( - JavaClass.Predicates.resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..") - .or( - JavaClass.Predicates.resideInAPackage("..adapter.outbound..") - .and( - JavaClass.Predicates.resideOutsideOfPackage( - "..adapter.outbound.persistence..")))) - .as( - "persistence-jpa is an internal (driven) outbound adapter — it must not reach into " - + "the web adapter, sibling outbound adapters, or the composition root " - + "(intra-persistence references are allowed).") - .allowEmptyShould(true); - - // ---- persistence-rdbms/no-sql-extensibility (RDBMS base vendor-neutrality) ---- - // adapter-persistence-rdbms (all of ..adapter.outbound.persistence.. EXCEPT - // ..adapter.outbound.persistence.postgresql..) - // must not import PostgreSQL driver or flyway-database-postgresql types. Those vendor-specific - // dependencies belong only in adapter-persistence-postgresql. - // - // Future NoSQL adapter modules (for example adapter-persistence-mongodb) must be added as - // sibling modules that implement application/domain ports directly. When such a module exists, - // add an ArchUnit rule forbidding jakarta.persistence.., org.hibernate.., - // org.springframework.data.jpa.., org.flywaydb.., and - // dev.caskeleton.adapter.outbound.persistence.postgresql.. - // dependencies from that module. - - /** - * The composition root reaches only the persistence leaf's exported surface. - * - *

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

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

{@code JpaNotificationRequestStore} imported {@code NotificationSubmissionService} and - * called its roll-up to work out what a request's status should be — so "one recipient completed - * and one failed means partially completed", a product rule, lived behind a JPA store, and - * changing it meant editing the application and the database layer in step. - * - *

The test is concreteness, not naming. An application interface named {@code ...Service} — - * {@code FileQuotaService}, for instance — is a port: the adapter implements it, so the - * dependency points from infrastructure to a contract, which is the direction this architecture - * wants. A concrete application service is orchestration, and an adapter that calls one has taken - * over a decision the use case owns. - */ - @ArchTest - static final ArchRule PERSISTENCE_DOES_NOT_DEPEND_ON_APPLICATION_SERVICES = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") - .should() - .dependOnClassesThat( - new com.tngtech.archunit.base.DescribedPredicate<>( - "application services or use cases") { - @Override - public boolean test(com.tngtech.archunit.core.domain.JavaClass candidate) { - String name = candidate.getName(); - return name.startsWith("dev.caskeleton.application.") - && !candidate.isInterface() - && (candidate.getSimpleName().endsWith("Service") - || candidate.getSimpleName().endsWith("UseCase")); - } - }) - .as( - "adapter:outbound:persistence-jpa must not depend on application services or use " - + "cases: an adapter that calls one has taken over a decision the use case owns.") - .allowEmptyShould(true); - - static final ArchRule PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") - .and() - .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("org.postgresql..", "org.flywaydb.database.postgresql..") - .as( - "adapter:outbound:persistence-jpa (non-postgresql) must stay vendor-neutral: " - + "PostgreSQL driver / flyway-database-postgresql types live only in the " - + ".postgresql subpackage of adapter:outbound:persistence-jpa.") - .allowEmptyShould(true); - - // The same fence around the second vendor. H2 arrives as a runtimeOnly driver, so today nothing - // imports org.h2.. and this rule holds vacuously — which is the point of writing it now: the - // first class that reaches for an H2 type has to do it inside the vendor package, before the - // "vendor-neutral base" claim quietly stops being true. - @ArchTest - static final ArchRule PERSISTENCE_RDBMS_STAYS_NEUTRAL_OF_H2 = - noClasses() - .that() - .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") - .and() - .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.h2..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("org.h2..") - .as( - "adapter:outbound:persistence-jpa (non-h2) must stay vendor-neutral: H2 driver types " - + "live only in the .h2 subpackage of adapter:outbound:persistence-jpa.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule PERSISTENCE_RDBMS_ENTITIES_DO_NOT_PIN_VENDOR_COLUMN_DEFINITIONS = - fields() - .that() - .areAnnotatedWith("jakarta.persistence.Column") - .and() - .areDeclaredInClassesThat() - .resideInAPackage("dev.caskeleton.adapter.outbound.persistence..") - .and() - .areDeclaredInClassesThat() - .resideOutsideOfPackage("dev.caskeleton.adapter.outbound.persistence.postgresql..") - .should(notDeclareColumnDefinition()) - .as( - "adapter:outbound:persistence-jpa (non-postgresql) must not pin vendor SQL " - + "columnDefinition strings; RDBMS physical types belong to vendor Flyway " - + "migrations, while rdbms entities may use portable JPA/Hibernate type hints.") - .allowEmptyShould(true); - - // NOTE: adapter.outbound.identifier AND adapter.outbound.persistence are both nested under - // adapter.outbound (sibling adapters, not top-level packages), so the rules below exclude each - // one's own package from the side that would otherwise match it against itself — mirroring the - // resideOutsideOfPackage sibling-exclusion style already used for - // PERSISTENCE_RDBMS_STAYS_VENDOR_NEUTRAL / PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES - // above. Without this, an intra-identifier or intra-persistence reference (a class referencing a - // class in its own nested adapter) would spuriously violate one of the rules. - - @ArchTest - static final ArchRule OUTBOUND_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_PERSISTENCE_ADAPTERS = - noClasses() - .that() - .resideInAPackage("..adapter.outbound..") - .and() - .resideOutsideOfPackage("..adapter.outbound.identifier..") - // persistence-jpa is itself a nested outbound adapter; exclude its own package from the - // selector so its intra-references (which land in the ..adapter.outbound.persistence.. - // forbidden target below) do not self-overlap. Generic outbound adapters (messaging, - // cache, http, ...) are still forbidden from depending on persistence. - .and() - .resideOutsideOfPackage("..adapter.outbound.persistence..") - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "..adapter.inbound.web..", - "..adapter.outbound.persistence..", - "..adapter.outbound.identifier..", - "..bootstrap..") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule IDENTIFIER_ADAPTER_DOES_NOT_DEPEND_ON_OTHER_ADAPTERS_OR_BOOTSTRAP = - noClasses() - .that() - .resideInAPackage("..adapter.outbound.identifier..") - .should() - .dependOnClassesThat( - JavaClass.Predicates.resideInAnyPackage( - "..adapter.inbound.web..", - "..adapter.outbound.persistence..", - "..bootstrap..", - "org.springframework.data.repository..", - "org.springframework.data.jpa.repository..", - "jakarta.persistence..", - "javax.persistence..", - "org.hibernate..") - .or( - JavaClass.Predicates.resideInAPackage("..adapter.outbound..") - .and( - JavaClass.Predicates.resideOutsideOfPackage( - "..adapter.outbound.identifier..")))) - .as( - "adapter:outbound:identifier is a non-IO driven adapter (UUIDv7 generation/codec) —" - + " it must not reach into sibling adapters, persistence, or the composition root" - + " (feature-resource-identifier-contract §4 taxonomy).") - .allowEmptyShould(true); - - // ---- Task 7: explicit inbound/outbound adapter topology (hexagonal driving/driven split) ---- - // The moves in Tasks 1–6 established the new package topology; the three rules below make it an - // ENFORCED invariant rather than a convention. The inbound (driving) and outbound (driven) - // adapter families must never reach across to each other — every interaction between them is - // mediated by an application port — and the outbound concerns are peers that share code only - // through :adapter:outbound:support. These generalise the per-adapter isolation rules above - // (WEB_..., PERSISTENCE_..., OUTBOUND_..., IDENTIFIER_...) into the two family-level directions. - - @ArchTest - static final ArchRule INBOUND_ADAPTERS_DO_NOT_DEPEND_ON_OUTBOUND_ADAPTERS = - noClasses() - .that() - .resideInAPackage("..adapter.inbound..") - .should() - .dependOnClassesThat() - .resideInAPackage("..adapter.outbound..") - .as( - "inbound (driving) adapters must not depend on outbound (driven) adapters — an " - + "inbound adapter drives the application through its inbound ports and reaches " - + "persistence / messaging / cache / http only via application outbound ports, " - + "never by touching an outbound adapter directly (Task 7 adapter topology)"); - - @ArchTest - static final ArchRule OUTBOUND_ADAPTERS_DO_NOT_DEPEND_ON_INBOUND_ADAPTERS = - noClasses() - .that() - .resideInAPackage("..adapter.outbound..") - .should() - .dependOnClassesThat() - .resideInAPackage("..adapter.inbound..") - .as( - "outbound (driven) adapters must not depend on inbound (driving) adapters — a driven " - + "adapter implements an application outbound port and is oblivious to which " - + "inbound adapter (web today, a future messaging-inbound tomorrow) triggered " - + "the call (Task 7 adapter topology)"); - - @ArchTest - static final ArchRule OUTBOUND_ADAPTERS_ARE_PEERS_SHARING_ONLY_SUPPORT = - slices() - .matching("..adapter.outbound.(*)..") - .namingSlices("$1") - .should() - .notDependOnEachOther() - // :adapter:outbound:support is the shared base (OutboundCorrelation, fail-open - // dependency logging, the @Configuration seam) that every outbound adapter is allowed - // to build on — a dependency whose TARGET resides in support is the sanctioned - // shared-code path, not peer-to-peer coupling, so it is ignored. Every other - // cross-slice edge (e.g. messaging -> cache, persistence -> httpclient) is forbidden. - .ignoreDependency( - DescribedPredicate.alwaysTrue(), - JavaClass.Predicates.resideInAPackage("..adapter.outbound.support..")) - .as( - "outbound adapters (persistence / identifier / cache / httpclient / messaging / " - + "notification) are peers: none may depend on a sibling; shared outbound code " - + "lives in :adapter:outbound:support (Task 7 adapter topology)"); - - /** - * A controller is a class Spring routes requests to, wherever it lives. - * - *

Selecting by the package string {@code ..adapter.inbound.web..controller..} was selecting a - * naming convention rather than a role. {@code NotificationCallbackMvcController} sits in {@code - * ..web.notification.platform.callback}, so the rules that forbid a controller from touching a - * repository or returning an entity never looked at it — the boundary was unguarded for exactly - * the endpoint an external provider posts to. - */ - private static final DescribedPredicate ARE_CONTROLLERS = - new DescribedPredicate<>("annotated as a Spring controller, or filed as one") { - @Override - public boolean test(JavaClass candidate) { - return candidate.isAnnotatedWith("org.springframework.web.bind.annotation.RestController") - || candidate.isAnnotatedWith("org.springframework.stereotype.Controller") - || candidate.isMetaAnnotatedWith( - "org.springframework.web.bind.annotation.RestController") - // The package stays in the predicate as well as the annotation. A class filed under - // ..controller.. is claiming the role whether or not Spring is currently routing to - // it, and dropping the package would narrow a rule this change exists to widen. - || (candidate.getPackageName().contains(".adapter.inbound.web") - && candidate.getPackageName().contains(".controller")); - } - }; - - /** A persistence entity is one the ORM maps, wherever it lives. */ - private static final DescribedPredicate ARE_PERSISTENCE_ENTITIES = - new DescribedPredicate<>("annotated as a persistence entity") { - @Override - public boolean test(JavaClass candidate) { - return candidate.isAnnotatedWith("jakarta.persistence.Entity") - || candidate.isAnnotatedWith("jakarta.persistence.MappedSuperclass") - || candidate.isAnnotatedWith("jakarta.persistence.Embeddable") - || candidate.isAnnotatedWith( - "org.springframework.data.mongodb.core.mapping.Document"); - } - }; - - /** A mapper is a translation type, identified by its name rather than its directory. */ - private static final DescribedPredicate ARE_PERSISTENCE_MAPPERS = - new DescribedPredicate<>("a persistence mapper by name") { - @Override - public boolean test(JavaClass candidate) { - return candidate.getSimpleName().endsWith("Mapper") - && candidate.getPackageName().contains(".adapter.outbound.persistence"); - } - }; - - @ArchTest - static final ArchRule CONTROLLERS_DO_NOT_ACCESS_REPOSITORIES_OR_JPA_ENTITIES = - noClasses() - .that(ARE_CONTROLLERS) - .should() - .dependOnClassesThat() - .resideInAnyPackage( - "..repository..", - "..adapter.outbound.persistence..", - "..adapter.outbound.persistence..entity..", - "org.springframework.data.repository..", - "org.springframework.data.jpa.repository..", - "jakarta.persistence..", - "javax.persistence.."); - - /** - * A datastore handle is a capability, and only the adapter that owns it may hold one. - * - *

The MongoDB guardrail existed as a set of forbidden type names with a test that asserted the - * set's contents. Nothing compared it to a class, so "a controller must not hold a MongoTemplate" - * was a passing test about a {@code Set} while every controller was governed by nothing — - * and Boot's own auto-configuration supplies a {@code MongoTemplate}, a {@code MongoClient} and a - * {@code MongoDatabase} as beans, so the injection was one constructor parameter away. - * - *

Checked here, at the composition root, because this is the only place where every production - * class is on one classpath: a rule that runs inside the Mongo leaf can only see the leaf, and - * the types it exists to keep out live everywhere else. - * - *

A driver handle bypasses the whole platform at once — collection profile, consistency - * profile, timeout budget, failure translation, observation — which is why the ban covers the - * driver packages rather than a list of class names that a new driver release can extend. - */ - @ArchTest - static final ArchRule ONLY_THE_PERSISTENCE_ADAPTER_HOLDS_A_MONGO_HANDLE = - noClasses() - .that() - .resideInAnyPackage( - "dev.caskeleton.domain..", - "dev.caskeleton.application..", - "dev.caskeleton.adapter.inbound..", - "dev.caskeleton.bootstrap..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("com.mongodb..", "org.springframework.data.mongodb..") - .as( - "only the MongoDB outbound adapter holds a MongoDB template, client, database or " - + "collection; every other layer reaches MongoDB through an application " - + "outbound port"); - - /** - * Which notification platform package may depend on which. - * - *

The design maps 31 logical modules onto packages inside five Gradle leaves, and the registry - * gate stops at the leaf boundary — so inside a leaf any package could reach any other and the - * layering existed only in the document. This table is the layering, and it is derived from the - * edges the code actually has: adding one means adding it here, in review, rather than - * discovering later that the module graph in the design and the import graph in the source - * disagree. - * - *

{@code api} depends on nothing, which is what makes it usable as the contract everything - * else shares. - */ - private static final Map> NOTIFICATION_PLATFORM_PACKAGE_EDGES = - Map.ofEntries( - Map.entry("api", Set.of()), - // port -> template: the template publication port speaks the platform's own template - // vocabulary — a slot, a published version — rather than restating it as strings. The - // edge - // is one-way; template depends on api alone, so nothing cycles through it. - Map.entry("port", Set.of("api", "template")), - // admin -> callback/dispatch/observation: the operator plane moved here from the outbound - // adapter, where operator authorization and transaction orchestration did not belong. - Map.entry( - "admin", Set.of("api", "callback", "dispatch", "observation", "policy", "provider")), - // callback -> port is an implementation depending on the interface it implements, which - // is the direction that makes the port a boundary. The reverse edge is what was removed. - Map.entry("callback", Set.of("api", "observation", "port")), - // dispatch -> port: the split inbound use cases live beside the service they wrap and - // implement the port interfaces callers depend on. - Map.entry( - "dispatch", - Set.of( - "api", - "port", - "callback", - "contact", - "observation", - "policy", - "provider", - "push", - "security", - "template")), - Map.entry("contact", Set.of("api")), - Map.entry("email", Set.of("api")), - Map.entry("inbox", Set.of("api")), - Map.entry("observation", Set.of("api")), - Map.entry("policy", Set.of("api", "contact", "provider")), - Map.entry("provider", Set.of("api", "callback", "security", "template")), - Map.entry("push", Set.of("api")), - Map.entry("security", Set.of("api", "contact")), - Map.entry("sms", Set.of("api", "contact")), - Map.entry("template", Set.of("api")), - Map.entry("webpush", Set.of("api"))); - - private static final String NOTIFICATION_PLATFORM_ROOT = - "dev.caskeleton.application.notification.platform"; - - @ArchTest - static final ArchRule NOTIFICATION_PLATFORM_PACKAGE_EDGES_ARE_REGISTERED = - classes() - .that() - .resideInAPackage(NOTIFICATION_PLATFORM_ROOT + "..") - .should(dependOnlyOnRegisteredNotificationPackages()) - .as( - "a notification platform package may depend only on the packages registered for it; " - + "the leaf-level registry cannot see inside a Gradle project, so the module " - + "graph is enforced here or nowhere (NTF-022)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NOTIFICATION_PLATFORM_PACKAGES_ARE_ACYCLIC = - slices() - .matching(NOTIFICATION_PLATFORM_ROOT + ".(*)..") - .namingSlices("$1") - .should() - .beFreeOfCycles() - .as( - "a cycle between two notification packages means neither can be understood, " - + "tested, or extracted without the other (NTF-022)"); - - @ArchTest - static final ArchRule NOTIFICATION_ADAPTER_PACKAGES_ARE_ACYCLIC = - slices() - .matching("dev.caskeleton.adapter.outbound.notification.platform.(*)..") - .namingSlices("$1") - .should() - .beFreeOfCycles() - .as("the outbound notification adapter's packages form a DAG too (NTF-022)"); - - /** - * Hold every notification platform package to its registered edge set. - * - * @return a condition that reports each unregistered package edge - */ - private static ArchCondition dependOnlyOnRegisteredNotificationPackages() { - return new ArchCondition<>("depend only on registered notification platform packages") { - @Override - public void check(JavaClass item, ConditionEvents events) { - String source = notificationPackageOf(item.getPackageName()); - if (source == null) { - return; - } - Set allowed = NOTIFICATION_PLATFORM_PACKAGE_EDGES.get(source); - if (allowed == null) { - events.add( - SimpleConditionEvent.violated( - item, - "notification platform package '" - + source - + "' is not registered in NOTIFICATION_PLATFORM_PACKAGE_EDGES: " - + item.getName())); - return; - } - for (JavaClass target : - item.getDirectDependenciesFromSelf().stream() - .map(dependency -> dependency.getTargetClass()) - .toList()) { - String edge = notificationPackageOf(target.getPackageName()); - if (edge == null || edge.equals(source) || allowed.contains(edge)) { - continue; - } - events.add( - SimpleConditionEvent.violated( - item, - "unregistered notification platform package edge " - + source - + " -> " - + edge - + " (" - + item.getName() - + " -> " - + target.getName() - + ")")); - } - } - }; - } - - /** - * The notification platform sub-package a class belongs to. - * - * @param packageName the class's package - * @return the first segment below the platform root, or null when the class is outside it - */ - private static String notificationPackageOf(String packageName) { - if (!packageName.startsWith(NOTIFICATION_PLATFORM_ROOT + ".")) { - return null; - } - String remainder = packageName.substring(NOTIFICATION_PLATFORM_ROOT.length() + 1); - int dot = remainder.indexOf('.'); - return dot < 0 ? remainder : remainder.substring(0, dot); - } - - @ArchTest - static final ArchRule PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER = - classes() - .that(ARE_PERSISTENCE_ENTITIES) - .should() - .resideInAnyPackage( - "..adapter.outbound.persistence..", - "..sample.portfolio.adapter.outbound.persistence..") - .as( - "a type the ORM maps belongs to a persistence adapter whatever it is called and " - + "wherever it is filed (NTF-022 semantic selection)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule WEB_DTOS_STAY_IN_WEB_ADAPTER = - classes() - .that() - .resideInAPackage("..adapter.inbound.web..dto..") - .should() - .onlyBeAccessed() - .byClassesThat() - .resideInAnyPackage("..adapter.inbound.web..") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES = - noClasses() - .that(ARE_PERSISTENCE_MAPPERS) - .should() - .dependOnClassesThat() - .resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..") - .allowEmptyShould(true); - - @ArchTest - // KNOWN DEFECT, deliberately left as found. Two things are wrong and they mask each other: the - // subject pattern "..shared.." matches any package named shared anywhere rather than this module, - // and the allowlist below holds the bare module root while the check matches by prefix — so every - // package inside dev.caskeleton.shared is blessed automatically. The rule therefore catches - // nothing in the module it is named after, and does catch unrelated leaves that happen to have a - // "shared" package. Fixing it needs the violation fixture to move inside dev.caskeleton.shared, - // which puts it in this rule's own analysis scope, which needs the fixture-scanning structure - // reworked. That is a separate change; see the Wave 1 progress note. - static final ArchRule SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES = - classes() - .that() - .resideInAPackage("..shared..") - .should(resideInAllowedSharedContractPackage()) - .allowEmptyShould(true); - - @ArchTest - static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO = - noClasses() - .that() - .resideOutsideOfPackage("..sample.portfolio..") - .should() - .dependOnClassesThat() - .resideInAPackage("..sample.portfolio.."); - - @ArchTest - static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .and() - .resideOutsideOfPackage("..fixtures..") - .should() - .dependOnClassesThat() - .resideInAPackage("..fixtures..") - .as( - "feature-test-taxonomy-fixture-contract D6: production code must never depend on a" - + " test fixture — fixtures live in src/test/.../fixtures/ (test-only); this" - + " guards against a fixture leaking onto the main classpath.") - .allowEmptyShould(true); - - // ---- feature-boundary-validation-mapping-contract ---- - - @ArchTest - static final ArchRule REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS = - classes() - .that() - .resideInAPackage("..adapter.inbound.web..dto..") - .should(notBeAnnotatedWithJsonIgnoreUnknown()) - .as( - "class-level @JsonIgnoreProperties(ignoreUnknown = true) on a request DTO " - + "silently swallows extra fields and defeats the B1 fail-fast boundary " - + "(feature-boundary-validation-mapping-contract B1)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName( - "com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator") - .as( - "LaissezFaireSubTypeValidator allows any subtype during polymorphic " - + "deserialization and is the CVE-2019-14379 RCE entry point " - + "(feature-boundary-validation-mapping-contract B5)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .callMethodWhere(target(name("enableDefaultTyping"))) - .as( - "ObjectMapper.enableDefaultTyping() (deprecated since Jackson 2.10) opens " - + "polymorphic deserialization to all classes — the documented " - + "CVE-2019-14379 RCE vector (feature-boundary-validation-mapping-contract B5). " - + "Use @JsonTypeInfo(use = NAME) + @JsonSubTypes, or " - + "activateDefaultTyping(BasicPolymorphicTypeValidator) with an explicit " - + "allowlist — the LaissezFaireSubTypeValidator class reference rule " - + "still blocks the unsafe variant.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_INHERITABLE_THREAD_LOCAL = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName("java.lang.InheritableThreadLocal") - .as( - "InheritableThreadLocal does not propagate cleanly to virtual threads " - + "and leaks context across pooled carrier threads — use MDC (SLF4J " - + "2.0+) or RequestContextHolder instead " - + "(feature-boundary-validation-mapping-contract B6)") - .allowEmptyShould(true); - - // ---- feature-runtime-context-propagation-contract (domain extension of B6) ---- - // Strategy guard for the domain-context propagation seam. The shipped default is - // ThreadLocal (DomainContextStrategy.THREAD_LOCAL — plain ThreadLocal, no preview - // API); the MICROMETER / SCOPED_VALUE strategies are reserved-but-unshipped. This - // rule keeps the two Java 21 PREVIEW primitives (ScopedValue / StructuredTaskScope) - // out of production code until the SCOPED_VALUE strategy is actually activated - // through the factory seam. The build has no --enable-preview (build fact C1), so - // those APIs cannot even compile today — the compile gate is the first line of - // defence and is why a compilable violation fixture is infeasible; this rule is the - // dormant defence-in-depth that fires the moment --enable-preview is enabled and - // someone reaches for the preview primitive without adding the strategy + factory - // case. It is NOT the deferred S4/D7 activation-correctness rule - // (UNSUPPORTED_IMPL_DECISION — rule shape intentionally not invented). The - // implicit-inheritance ban (S3, the ThreadLocal default's contract) cross-cites - // NO_INHERITABLE_THREAD_LOCAL above (owner: boundary-validation B6). - - @ArchTest - static final ArchRule DOMAIN_CONTEXT_PROPAGATION_PRIMITIVES_STAY_UNSHIPPED = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .haveNameMatching( - "(java\\.lang\\.ScopedValue" - + "|java\\.util\\.concurrent\\.StructuredTaskScope)(\\$.*)?") - .as( - "the domain-context default is ThreadLocal; the ScopedValue / " - + "StructuredTaskScope (preview) strategy must not appear in production " - + "code until activated through the DomainContextStrategy + factory seam " - + "(feature-runtime-context-propagation-contract S2)") - .allowEmptyShould(true); - - // ---- feature-boundary-validation-mapping-contract — 판정 기준 §Forbidden + 테스트 계약 ---- - - @ArchTest - static final ArchRule CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES = - methods() - .that() - .areDeclaredInClassesThat(ARE_CONTROLLERS) - .and() - .arePublic() - .should() - .notHaveRawReturnType( - JavaClass.Predicates.resideInAnyPackage( - "..domain..", - "..adapter.outbound.persistence.entity..", - "..adapter.outbound.persistence.repository..")) - .as( - "controller methods must return DTO / record / Envelope / ResponseEntity — " - + "never a domain entity, JPA entity, or repository type " - + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule APPLICATION_METHODS_DO_NOT_ACCEPT_WEB_DTOS = - methods() - .that() - .areDeclaredInClassesThat() - .resideInAPackage("..application..") - .and() - .arePublic() - .should() - .notHaveRawParameterTypes( - DescribedPredicate.describe( - "any in ..adapter.inbound.web..dto..", - (List params) -> { - for (JavaClass p : params) { - if (JavaClass.Predicates.resideInAPackage("..adapter.inbound.web..dto..") - .test(p)) { - return true; - } - } - return false; - })) - .as( - "application methods must not accept request/response DTOs — " - + "controllers must translate to Command/Query before invoking the use case " - + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_PROBLEM_DETAIL_USAGE = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName("org.springframework.http.ProblemDetail") - .as( - "D5: RFC 7807 ProblemDetail is explicitly rejected — error responses must use " - + "the shared Envelope shape " - + "(feature-boundary-validation-mapping-contract D5). Production code is " - + "scanned only — test fixtures in ..architecture.violations.. are out of " - + "scope under ImportOption.DoNotIncludeTests.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING = - classes() - .that() - .resideInAPackage("dev.caskeleton..") - .should(notReferenceMediaTypeString("application/merge-patch+json")) - .as( - "B2: RFC 7396 application/merge-patch+json content type is explicitly rejected " - + "— PATCH endpoints must use JSON with JsonNullable 3-state " - + "(feature-boundary-validation-mapping-contract B2)") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES = - methods() - .that() - .areDeclaredInClassesThat( - JavaClass.Predicates.resideInAPackage("..adapter.outbound..") - .and(implementsApplicationLayerPort())) - // @Configuration factory classes are excluded: a @Bean method legitimately - // returns the adapter's own outbound port type for DI wiring (e.g. - // MessagingConfig#messagePublisher -> MessagePublisher) — that is - // composition-root assembly, not an adapter *response* method leaking an - // external type. B7 targets the latter (ACL leakage), not DI factories - // (feature-integration-adapter-templates Layer 1 @ConditionalOnProperty configs). - .and() - .areDeclaredInClassesThat() - .areNotAnnotatedWith("org.springframework.context.annotation.Configuration") - // @ConfigurationProperties settings holders are excluded for the same reason as - // @Configuration factories: they are config-binding types, not adapter *response* - // surfaces. Their accessors return bound config values (primitives, Duration, - // DataSize) and nested config records (e.g. OutboundHttpSettings#retry -> - // OutboundHttpSettings.Retry) — internal settings, not external HTTP / messaging - // responses, so B7's ACL-leakage target does not apply. - .and() - .areDeclaredInClassesThat() - .areNotAnnotatedWith( - "org.springframework.boot.context.properties.ConfigurationProperties") - // The canonical HTTP control plane and explicit legacy factories have a small exact - // method whitelist below. Names, packages, and arbitrary @Bean annotations do not bypass - // B7. - .and(notAnExactHttpControlPlaneMethod()) - // Redis provider binding and secret material provider SPIs likewise expose only a small - // exact set of typed composition accessors. Command/router/native runtime and secret - // value accessors are deliberately not exempt. - .and(notAnExactRedisCompositionMethod()) - .and() - // Package-private implementation types cannot expose their methods outside the - // adapter package. B7 protects the externally reachable adapter API, not internal - // records and fault-injection seams used to implement that API. - .areDeclaredInClassesThat() - .arePublic() - .and() - .arePublic() - .and() - .areNotStatic() - .should() - .notHaveRawReturnType( - JavaClass.Predicates.resideInAnyPackage( - "..adapter.outbound..", - "..adapter.inbound.web..", - "..adapter.outbound.persistence..")) - .as( - "B7: outbound application-port adapter public methods must return domain types" - + " (or primitives/wrappers/Optional) — raw external response types must not" - + " escape the adapter package (feature-boundary-validation-mapping-contract B7" - + " ACL). @Configuration @Bean factory methods and @ConfigurationProperties" - + " settings holders are excluded — they assemble port bindings / bind config," - + " not adapter response surfaces.") - .allowEmptyShould(true); - - private static final Set B7_EXACT_HTTP_CONTROL_PLANE_METHODS = - // The HTTP Client Platform (feature-httpclient-platform) exposes no outbound adapter - // class that implements an application-layer port, so B7 has no HTTP control-plane - // method to exempt. The whitelist mechanism stays in place so a future application-port - // implementation must justify each exact accessor rather than widen the rule. - Set.of(); - - private static DescribedPredicate notAnExactHttpControlPlaneMethod() { - return new DescribedPredicate<>("not an exact HTTP control-plane accessor/factory") { - @Override - public boolean test(JavaMethod method) { - return !B7_EXACT_HTTP_CONTROL_PLANE_METHODS.contains(ExactMethodSignature.from(method)); - } - }; - } - - private static final Set B7_EXACT_REDIS_COMPOSITION_METHODS = - Set.of( - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings", - "dataAuthentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings", - "dataTls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone", - "dataAuthentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone", - "dataTls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", - "sentinelAuthentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", - "sentinelTls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", - "dataAuthentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", - "dataTls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster", - "dataAuthentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster", - "dataTls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", - "topology", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$Topology"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", - "standalone", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$StandaloneProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", - "sentinel", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", - "cluster", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$ClusterProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", - "authentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", - "tls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties", - "authentication", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties", - "tls", - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$RuntimeProperties", - "clientSettings", - "dev.caskeleton.adapter.outbound.cache.redis.runtime.RedisClientRuntimeSettings"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider", - "resolve", - List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"), - "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial"), - signature( - "dev.caskeleton.adapter.outbound.cache.redis.security.RedisTrustMaterialProvider", - "resolve", - List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"), - "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisTrustMaterial")); - - private static DescribedPredicate notAnExactRedisCompositionMethod() { - return new DescribedPredicate<>("not an exact Redis composition accessor or material SPI") { - @Override - public boolean test(JavaMethod method) { - return !B7_EXACT_REDIS_COMPOSITION_METHODS.contains(ExactMethodSignature.from(method)); - } - }; - } - - private static ExactMethodSignature signature(String owner, String name, String returnType) { - return signature(owner, name, List.of(), returnType); - } - - private static ExactMethodSignature signature( - String owner, String name, List parameters, String returnType) { - return new ExactMethodSignature(owner, name, parameters, returnType); - } - - private record ExactMethodSignature( - String owner, String name, List parameters, String returnType) { - - private ExactMethodSignature { - parameters = List.copyOf(parameters); - } - - private static ExactMethodSignature from(JavaMethod method) { - List parameterTypes = - method.getRawParameterTypes().stream().map(JavaClass::getFullName).toList(); - return new ExactMethodSignature( - method.getOwner().getFullName(), - method.getName(), - parameterTypes, - method.getRawReturnType().getFullName()); - } - } - - @ArchTest - static final ArchRule - MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS = - methods() - .that() - .areDeclaredInClassesThat() - .resideInAPackage("..adapter.outbound.messaging..") - .and() - .areDeclaredInClassesThat() - .areNotAnnotatedWith("org.springframework.context.annotation.Configuration") - .and() - .areDeclaredInClassesThat() - .areNotAnnotatedWith( - "org.springframework.boot.context.properties.ConfigurationProperties") - .and() - .arePublic() - .and() - .areNotStatic() - .should( - notReturnAdapterTypesIncludingGenericArguments( - "..adapter.outbound..", - "..adapter.inbound.web..", - "..adapter.outbound.persistence..")) - .as( - "B7 messaging hardening: every public non-static messaging method must keep " - + "adapter-local types out of direct and recursive generic return positions; " - + "narrow public static composition bridges are the only allowed adapter-local " - + "return seam") - .allowEmptyShould(true); - - private static ArchCondition notReturnAdapterTypesIncludingGenericArguments( - String... forbiddenPackages) { - DescribedPredicate forbidden = - JavaClass.Predicates.resideInAnyPackage(forbiddenPackages); - return new ArchCondition<>( - "not return adapter-local types directly or through generic arguments") { - @Override - public void check(JavaMethod method, ConditionEvents events) { - for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) { - if (forbidden.test(involved)) { - events.add( - SimpleConditionEvent.violated( - method, - "Method " - + method.getFullName() - + " leaks " - + involved.getName() - + " through its return type (directly or as a generic argument)" - + " — B7 permits only narrow static composition bridges for " - + "adapter-local types")); - } - } - } - }; - } - - private static DescribedPredicate implementsApplicationLayerPort() { - return DescribedPredicate.describe( - "implement an application-layer port", - type -> - type.getAllRawInterfaces().stream() - .anyMatch( - iface -> - iface.getPackageName().contains(".application.") - || iface.getPackageName().endsWith(".application"))); - } - - @ArchTest - static final ArchRule VALID_CASCADE_DEPTH_AT_MOST_THREE = - classes() - .that() - .resideInAPackage("..adapter.inbound.web..dto..") - .should(notHaveValidCascadeDeeperThan(3)) - .as( - "B4: @Valid cascade depth must be ≤ 3 to bound nested-DTO recursion cost " - + "(feature-boundary-validation-mapping-contract B4 — DoS defense)") - .allowEmptyShould(true); - - // ---- feature-business-rule-validation-contract C1/D1 (validation stays at the web boundary) - // ---- - - @ArchTest - static final ArchRule VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY = - noClasses() - .that() - .resideInAnyPackage("..domain..", "..application..") - .should() - .dependOnClassesThat() - .resideInAPackage("jakarta.validation..") - .as( - "C1/D1: Bean Validation (jakarta.validation @NotNull / @Valid / ...) is the " - + "syntax layer and must stay at the adapter-web request boundary. Domain " - + "invariants live in domain model / value-object constructors and application " - + "policy is plain Java — neither is expressed with annotation-driven " - + "validation (feature-business-rule-validation-contract C1/D1, boundary B4 " - + "layering). Production code is scanned only — fixtures in " - + "..architecture.violations.. are out of scope under " - + "ImportOption.DoNotIncludeTests.") - .allowEmptyShould(true); - - // ---- feature-api-contract-baseline D19 (resource URL naming, AIP-122) ---- - - @ArchTest - static final ArchRule CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122 = - classes() - .that() - .resideInAPackage("..adapter.inbound.web..controller..") - .should(haveAip122CompliantRequestMappingPaths()) - .as( - "D19: controller @RequestMapping path segments must follow AIP-122 — lowercase " - + "first char + ASCII alphanumerics ([a-z][a-zA-Z0-9]*), so kebab-case " - + "(/work-logs), CamelCase (/Tickets) and SNAKE_CASE are rejected. " - + "{pathVariables} and AIP-136 colon-verb custom methods " - + "(/worklogs:batchCreate) are allowed (feature-api-contract-baseline D19)") - .allowEmptyShould(true); - - private static final Pattern AIP122_SEGMENT = Pattern.compile("[a-z][a-zA-Z0-9]*"); - private static final Pattern AIP136_VERB = Pattern.compile("[a-zA-Z][a-zA-Z0-9]*"); - private static final Set MAPPING_ANNOTATION_TYPES = - Set.of( - "org.springframework.web.bind.annotation.RequestMapping", - "org.springframework.web.bind.annotation.GetMapping", - "org.springframework.web.bind.annotation.PostMapping", - "org.springframework.web.bind.annotation.PutMapping", - "org.springframework.web.bind.annotation.PatchMapping", - "org.springframework.web.bind.annotation.DeleteMapping"); - - private static ArchCondition haveAip122CompliantRequestMappingPaths() { - return new ArchCondition<>("have AIP-122 compliant @RequestMapping path segments") { - @Override - public void check(JavaClass item, ConditionEvents events) { - checkMappingPaths(item, item.getAnnotations(), events); - for (JavaMethod m : item.getMethods()) { - checkMappingPaths(item, m.getAnnotations(), events); - } - } - }; - } - - private static void checkMappingPaths( - JavaClass owner, Set> annotations, ConditionEvents events) { - for (JavaAnnotation annotation : annotations) { - if (!MAPPING_ANNOTATION_TYPES.contains(annotation.getRawType().getName())) { - continue; - } - for (String key : List.of("value", "path")) { - annotation - .get(key) - .ifPresent( - value -> { - for (String path : toPathStrings(value)) { - for (String segment : path.split("/", 0)) { - validatePathSegment(owner, segment, path, events); - } - } - }); - } - } - } - - private static List toPathStrings(Object value) { - if (value instanceof String s) { - return List.of(s); - } - if (value instanceof Object[] arr) { - List out = new ArrayList<>(); - for (Object e : arr) { - if (e != null) { - out.add(e.toString()); - } - } - return out; - } - return List.of(); - } - - private static void validatePathSegment( - JavaClass owner, String segment, String path, ConditionEvents events) { - if (segment.isEmpty() || (segment.startsWith("{") && segment.endsWith("}"))) { - return; // empty segment or {pathVariable} - } - String[] colonParts = segment.split(":", 2); - String resource = colonParts[0]; - if (!resource.isEmpty() && !AIP122_SEGMENT.matcher(resource).matches()) { - events.add( - SimpleConditionEvent.violated( - owner, - "Class " - + owner.getName() - + " maps non-AIP-122 path segment '" - + segment - + "' in '" - + path - + "' — use lowercase [a-z][a-zA-Z0-9]* (no kebab/Camel/snake)")); - return; - } - if (colonParts.length == 2 && !AIP136_VERB.matcher(colonParts[1]).matches()) { - events.add( - SimpleConditionEvent.violated( - owner, - "Class " - + owner.getName() - + " maps non-AIP-136 colon-verb '" - + segment - + "' in '" - + path - + "'")); - } - } - - private static ArchCondition notReferenceMediaTypeString(String mediaType) { - return new ArchCondition<>("not reference media type string '" + mediaType + "'") { - @Override - public void check(JavaClass item, ConditionEvents events) { - // The realistic carriers — @RequestMapping(consumes=...), - // @PostMapping(consumes=...), and so on — sit on classes AND methods. - // Bytecode does not retain String initializer values for non-final - // fields, so free-form references inside method bodies cannot be - // detected at the ArchUnit level. We catch the annotation carriers - // (class and method) which is where the realistic usage lives. - scanAnnotations(item, item.getAnnotations(), mediaType, events); - for (JavaMethod m : item.getMethods()) { - scanAnnotations(item, m.getAnnotations(), mediaType, events); - } - } - }; - } - - private static void scanAnnotations( - JavaClass owner, - Set> annotations, - String mediaType, - ConditionEvents events) { - for (JavaAnnotation annotation : annotations) { - // Annotation properties on Spring's @RequestMapping family carry their values as - // String[] which boxes to Object[]. Some ArchUnit versions also expose property - // values as plain String. Walk both shapes and also fall back to the rendered - // annotation text so single-value carriers (e.g. MediaType constants resolved at - // bytecode level) are caught. - for (Object v : annotation.getProperties().values()) { - if (matches(v, mediaType)) { - events.add( - SimpleConditionEvent.violated( - owner, - "Class " - + owner.getName() - + " uses '" - + mediaType - + "' via @" - + annotation.getRawType().getSimpleName())); - return; - } - } - if (annotation.toString().contains(mediaType)) { - events.add( - SimpleConditionEvent.violated( - owner, - "Class " - + owner.getName() - + " uses '" - + mediaType - + "' (detected in @" - + annotation.getRawType().getSimpleName() - + " text)")); - } - } - } - - private static boolean matches(Object value, String mediaType) { - if (value == null) { - return false; - } - if (value instanceof String s) { - return s.equals(mediaType); - } - if (value instanceof Object[] arr) { - for (Object e : arr) { - if (matches(e, mediaType)) { - return true; - } - } - } - if (value instanceof Iterable iter) { - for (Object e : iter) { - if (matches(e, mediaType)) { - return true; - } - } - } - // Fallback: rendered representation. Catches Spring annotation values that - // get wrapped in opaque ArchUnit holders whose toString carries the literal. - return value.toString().contains(mediaType); - } - - private static ArchCondition notHaveValidCascadeDeeperThan(int maxDepth) { - return new ArchCondition<>("not have @Valid cascade deeper than " + maxDepth + " nested DTOs") { - @Override - public void check(JavaClass item, ConditionEvents events) { - int depth = computeValidDepth(item, new HashSet<>(), 0); - if (depth > maxDepth) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " has @Valid cascade depth " - + depth - + " (max allowed: " - + maxDepth - + ")")); - } - } - - private int computeValidDepth(JavaClass type, Set visited, int currentDepth) { - if (!visited.add(type.getName())) { - return currentDepth; - } - int maxFound = currentDepth; - for (JavaField field : type.getFields()) { - boolean hasValid = false; - for (JavaAnnotation a : field.getAnnotations()) { - if (a.getRawType().getName().equals("jakarta.validation.Valid")) { - hasValid = true; - break; - } - } - if (hasValid) { - JavaClass fieldType = field.getRawType(); - if (fieldType.getPackageName().startsWith("dev.caskeleton")) { - int sub = computeValidDepth(fieldType, visited, currentDepth + 1); - if (sub > maxFound) { - maxFound = sub; - } - } - } - } - return maxFound; - } - }; - } - - private static ArchCondition notBeAnnotatedWithJsonIgnoreUnknown() { - return new ArchCondition<>( - "not be annotated with @JsonIgnoreProperties(ignoreUnknown = true)") { - @Override - public void check(JavaClass item, ConditionEvents events) { - item.tryGetAnnotationOfType("com.fasterxml.jackson.annotation.JsonIgnoreProperties") - .ifPresent( - annotation -> { - Object ignoreUnknown = annotation.get("ignoreUnknown").orElse(null); - if (Boolean.TRUE.equals(ignoreUnknown)) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " is annotated with " - + "@JsonIgnoreProperties(ignoreUnknown = true) — " - + "use field-level @JsonIgnore on the specific " - + "field instead (B1)")); - } - }); - } - }; - } - - // ---- feature-resource-identifier-contract D17 (decision SSOT: resource-identifier branch) ---- - - @ArchTest - static final ArchRule NO_LONG_ID_PK = - fields() - .that() - .areDeclaredInClassesThat() - .resideInAPackage("..domain..") - .and() - .haveNameMatching("id") - .should() - .haveRawType(assignableTo(ResourceId.class)) - .as( - "D17 NO_LONG_ID_PK: a domain entity 'id' field must be a ResourceId value object" - + " (e.g. WorkLogId), never Long/long/int/Integer" - + " (feature-resource-identifier-contract D17). JPA @Id UUID columns in" - + " ..adapter.outbound.persistence.. are out of scope — they store the UUID as" - + " the PostgreSQL native uuid type per D10.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_UUID_RANDOM_IN_CONTROLLER = - noClasses() - .that() - .resideInAnyPackage("..adapter.inbound.web..controller..", "..application..") - .should() - .callMethod(UUID.class, "randomUUID") - .orShould() - .dependOnClassesThat() - .haveFullyQualifiedName("com.github.f4b6a3.uuid.UuidCreator") - .as( - "D5/D17 NO_UUID_RANDOM_IN_CONTROLLER: controllers and the application layer " - + "(use cases / services) must not generate resource identifiers directly " - + "(UUID.randomUUID / UuidCreator) — inject the domain WorkLogIdFactory port " - + "instead (feature-resource-identifier-contract D5/D17). Scope follows the " - + "D17 decision text (controller / service / use case); request/trace-id " - + "generation in web filters is a distinct concern owned by D18 / " - + "feature-distributed-tracing-contract and is intentionally out of scope.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_MATH_RANDOM_FOR_ID = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .callMethod(Math.class, "random") - .as( - "D9/D17 NO_MATH_RANDOM_FOR_ID: Math.random() is not a CSPRNG and must never " - + "seed an identifier (feature-resource-identifier-contract D9/D17).") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_VARCHAR_255_FOR_ID_COLUMN = - fields() - .that() - .areAnnotatedWith("jakarta.persistence.Column") - .and() - .haveNameMatching(".*[iI]d") - .should(haveExplicitIdColumnMapping()) - .as( - "D17 NO_VARCHAR_255_FOR_ID_COLUMN: an @Column-mapped id field must declare an " - + "explicit non-default length or a portable UUID type hint — never fall " - + "back to varchar(255) (feature-resource-identifier-contract D17).") - .allowEmptyShould(true); - - // ---- feature-streaming-response-contract D3 (event/server-push streaming ban) ---- - - @ArchTest - static final ArchRule NO_SSE_EMITTER = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName( - "org.springframework.web.servlet.mvc.method.annotation.SseEmitter") - .as( - "D3 NO_SSE_EMITTER: production code must not import SseEmitter — " - + "SSE server-push streaming (W3C EventSource / text/event-stream) is " - + "not supported in ca-skeleton (feature-streaming-response-contract D3). " - + "Production code is scanned only — test fixtures in " - + "..architecture.violations.. are out of scope under " - + "ImportOption.DoNotIncludeTests.") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_RESPONSE_BODY_EMITTER = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .haveFullyQualifiedName( - "org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter") - .as( - "D3 NO_RESPONSE_BODY_EMITTER: production code must not import " - + "ResponseBodyEmitter — it is the SSE base class and incremental " - + "object-emit surface (SPRING-ASYNC-C3) treated as server-push " - + "(UNSUPPORTED_IMPL_DECISION④). Note: StreamingResponseBody " - + "(large-file download, request-response model) is NOT blocked — " - + "it is owned by feature-file-resource-handling-contract D8 " - + "(feature-streaming-response-contract D3).") - .allowEmptyShould(true); - - @ArchTest - static final ArchRule NO_WEBSOCKET_HANDLER = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .dependOnClassesThat() - .resideInAnyPackage("org.springframework.web.socket..", "jakarta.websocket..") - .as( - "D3 NO_WEBSOCKET_HANDLER: production code must not depend on " - + "org.springframework.web.socket.. or jakarta.websocket.. — " - + "these cover the spring-websocket handler/STOMP surfaces and " - + "Jakarta @ServerEndpoint (RFC 6455 full-duplex WebSocket model). " - + "Full-duplex streaming is not supported in ca-skeleton " - + "(feature-streaming-response-contract D3). Production code is " - + "scanned only under ImportOption.DoNotIncludeTests.") - .allowEmptyShould(true); - - // ---- feature-secrets-config-source-contract D3/D10 (no-runtime-reload static enforcement) ---- - - @ArchTest - static final ArchRule NO_REFRESH_SCOPE_ANYWHERE = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .beAnnotatedWith("org.springframework.cloud.context.config.annotation.RefreshScope") - .as( - "D3/D10 NO_REFRESH_SCOPE_ANYWHERE: secrets default to no-runtime-reload " - + "(rotation = restart validation), so @RefreshScope is banned across the " - + "whole production tree — it is the in-process config-reload machinery the " - + "VAULT-DYN-C2 dynamic-reload rejection forbids " - + "(feature-secrets-config-source-contract D3/D10). The ban is total (no " - + "carve-out): an explicit rotation handler (e.g. JWT 24h overlap, owned by " - + "another branch) rotates via a mutable holder + scheduled swap, never " - + "@RefreshScope. spring-cloud-context is not on the production classpath, so " - + "the annotation is referenced by fully-qualified name. Production code is " - + "scanned only — the fixture in ..architecture.violations.secrets.. is out " - + "of scope under ImportOption.DoNotIncludeTests.") - .allowEmptyShould(true); - - // ---- feature-schema-serialization-contract D3 (BigDecimal precision trap ban) ---- - - @ArchTest - static final ArchRule NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR = - noClasses() - .that() - .resideInAPackage("dev.caskeleton..") - .should() - .callConstructor(BigDecimal.class, double.class) - .orShould() - .callConstructor(BigDecimal.class, float.class) - .as( - "D3 NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR: new BigDecimal(double) / " - + "new BigDecimal(float) captures the binary floating-point error " - + "(new BigDecimal(0.1) == 0.1000000000000000055...) instead of the exact " - + "decimal — money/decimal values must be built from a String " - + "(new BigDecimal(\"0.1\") or BigDecimal.valueOf(double)) " - + "(feature-schema-serialization-contract D3 / SBMS-C3). Production code is " - + "scanned only — test fixtures in ..architecture.violations.. are out of " - + "scope under ImportOption.DoNotIncludeTests.") - .allowEmptyShould(true); - - // ---- feature-application-query-bypass-contract D1 (read/query port purity guardrail) ---- - - @ArchTest - static final ArchRule QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES = - methods() - .that() - .areDeclaredInClassesThat( - JavaClass.Predicates.resideInAPackage("..application..") - .and(JavaClass.Predicates.simpleNameEndingWith("QueryPort"))) - .and() - .arePublic() - .should( - notLeakDomainJpaOrWebThroughReturnType( - "..domain..", - "..adapter..", - "jakarta.persistence..", - "javax.persistence..", - "org.springframework.web..", - "org.hibernate..")) - .as( - "D1 QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES: application read/query ports" - + " (classes whose simple name ends with 'QueryPort') must return" - + " application-layer projection DTOs — never a domain aggregate, JPA entity, or" - + " web type, INCLUDING through generic type arguments like List" - + " (checked via JavaType.getAllInvolvedRawTypes(), since a raw-return-type check" - + " alone misses generic leakage) (feature-application-query-bypass-contract D1" - + " core purity guardrail). The through-aggregate read path (repository ports" - + " returning the domain aggregate) is a separate, equally-valid choice and is" - + " intentionally out of this rule's scope.") - .allowEmptyShould(true); - - private static ArchCondition notLeakDomainJpaOrWebThroughReturnType( - String... forbiddenPackages) { - DescribedPredicate forbidden = - JavaClass.Predicates.resideInAnyPackage(forbiddenPackages); - return new ArchCondition<>( - "return application projection DTOs only " - + "(no domain / adapter / JPA / web types, including generic type arguments)") { - @Override - public void check(JavaMethod method, ConditionEvents events) { - // getAllInvolvedRawTypes() flattens the return type plus every generic type - // argument (recursively) to their raw classes — so List surfaces - // both java.util.List AND DomainType, which a raw-return-type predicate misses. - for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) { - if (forbidden.test(involved)) { - events.add( - SimpleConditionEvent.violated( - method, - "Method " - + method.getFullName() - + " leaks " - + involved.getName() - + " through its return type (directly or as a generic type argument)" - + " — a *QueryPort must return application-layer projection DTOs " - + "(feature-application-query-bypass-contract D1)")); - } - } - } - }; - } - - // ---- feature-domain-modeling-guardrails custom conditions ---- - - private static final Set AUDIT_FIELD_NAMES = - Set.of("createdat", "updatedat", "createdby", "updatedby"); - - private static ArchCondition declareAnAuditField() { - return new ArchCondition<>( - "declare an audit field " + "(createdAt / updatedAt / createdBy / updatedBy)") { - @Override - public void check(JavaClass item, ConditionEvents events) { - for (JavaField field : item.getFields()) { - if (AUDIT_FIELD_NAMES.contains(field.getName().toLowerCase(Locale.ROOT))) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " declares audit field '" - + field.getName() - + "' — audit metadata must live only on the adapter-persistence " - + "AuditableEntity @MappedSuperclass, not on a domain class " - + "(feature-persistence-auditing-contract D2)")); - } - } - } - }; - } - - private static ArchCondition notHaveAPublicNoArgConstructor() { - return new ArchCondition<>("not have a public no-arg constructor") { - @Override - public void check(JavaClass item, ConditionEvents events) { - for (JavaConstructor constructor : item.getConstructors()) { - if (constructor.getRawParameterTypes().isEmpty() - && constructor.getModifiers().contains(JavaModifier.PUBLIC)) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " exposes a public no-arg constructor — " - + "a value object must validate its invariant on the sole " - + "construction path and offer no empty back door " - + "(feature-domain-modeling-guardrails D5/D6)")); - } - } - } - }; - } - - private static ArchCondition beRecordTypes() { - return new ArchCondition<>("be a record") { - @Override - public void check(JavaClass item, ConditionEvents events) { - if (!item.isRecord()) { - events.add( - SimpleConditionEvent.violated( - item, - "Class " - + item.getName() - + " is annotated @DomainEvent but is not a " - + "record — a domain event is an immutable fact and must be a " - + "record (feature-domain-modeling-guardrails D4/D8)")); - } - } - }; - } - - private static ArchCondition notDeclareColumnDefinition() { - return new ArchCondition<>("not declare @Column columnDefinition") { - @Override - public void check(JavaField field, ConditionEvents events) { - field - .tryGetAnnotationOfType("jakarta.persistence.Column") - .ifPresent( - column -> { - String columnDefinition = - column.get("columnDefinition").map(Object::toString).orElse(""); - if (!columnDefinition.isBlank()) { - events.add( - SimpleConditionEvent.violated( - field, - "Field " - + field.getFullName() - + " pins vendor SQL columnDefinition='" - + columnDefinition - + "' in adapter:outbound:persistence-jpa; move the " - + "physical type to the vendor migration.")); - } - }); - } - }; - } - - private static ArchCondition haveExplicitIdColumnMapping() { - return new ArchCondition<>( - "declare an explicit non-default length or portable UUID type hint") { - @Override - public void check(JavaField field, ConditionEvents events) { - field - .tryGetAnnotationOfType("jakarta.persistence.Column") - .ifPresent( - column -> { - int length = column.get("length").map(v -> ((Number) v).intValue()).orElse(255); - boolean explicit = length != 255 || hasPortableUuidTypeHint(field); - if (!explicit) { - events.add( - SimpleConditionEvent.violated( - field, - "Field " - + field.getFullName() - + " maps an id column with the implicit " - + "varchar(255) default — declare an explicit length or " - + "@JdbcTypeCode(SqlTypes.UUID) for UUID fields " - + "(feature-resource-identifier-contract D17)")); - } - }); - } - }; - } - - private static boolean hasPortableUuidTypeHint(JavaField field) { - if (!field.getRawType().isEquivalentTo(UUID.class)) { - return false; - } - return field - .tryGetAnnotationOfType("org.hibernate.annotations.JdbcTypeCode") - .flatMap(annotation -> annotation.get("value")) - .filter(value -> value instanceof Number number && number.intValue() == SqlTypes.UUID) - .isPresent(); - } -} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java new file mode 100644 index 00000000..95666fa6 --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CompositionRootBoundaryArchitectureTest.java @@ -0,0 +1,83 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +/** + * The composition root assembles; it does not implement application ports (BOOT-017). + * + *

A class that implements an {@code application-core} port over an external system is an + * outbound adapter, and an outbound adapter belongs to an {@code adapter/outbound/*} leaf. Four + * such implementations had accumulated here — two Micrometer metric sinks and two provider + * registries — and nothing objected, because the composition root is where their inputs happen to + * be assembled and that felt like a reason. + * + *

It is not one. This repository already had the counter-example in place: {@code + * LoggingNotificationAudit} implements {@code NotificationAuditPort} and lives in {@code + * adapter/outbound/notification}, with the composition root doing nothing but wire it. The + * Micrometer sink next to it now does the same. Without a rule, the pressure only ever runs one way + * — every new observability port lands in bootstrap because the previous one did — and a derived + * service that replaces the composition root inherits adapter implementations it has to rewrite. + * + *

What this rule does not cover

+ * + *

{@code shared-contract} seams are excluded deliberately. That leaf holds skeleton-wide + * operational contracts — tracing, metrics naming, the runtime environment vocabulary — which the + * composition root is a legitimate participant in rather than a consumer of. Extending the rule + * there would forbid the composition root from implementing contracts that exist for it. + * + *

Bootstrap-local interfaces are also outside it. {@code EnvironmentSecretSource} implements + * {@code dev.caskeleton.bootstrap.runtime.SecretSource}, a port this leaf declares and owns; a + * strategy over a leaf's own interface is not a misplaced adapter, and an earlier reading that + * counted it as one was simply wrong about where the interface lived. + */ +@AnalyzeClasses( + packages = "dev.caskeleton.bootstrap", + importOptions = ProductionClassImportOption.class) +class CompositionRootBoundaryArchitectureTest { + + /** + * {@code MeteredDistributedLockPort} — a decorator, not an adapter. + * + *

It implements {@code DistributedLockPort} in order to wrap another implementation of it with + * metering, and "which ports are metered" is an assembly decision, which is this leaf's job. The + * argument on the other side is real — metering is a cross-cutting concern like retry or caching, + * and those collecting here is how a composition root becomes large again — so the threshold is + * stated rather than left to taste: at the second such decorator, they move to {@code + * adapter:outbound:support} together. One is a special case; two is a category. + */ + private static final String METERED_DISTRIBUTED_LOCK_PORT = + "dev.caskeleton.bootstrap.autoconfigure.lock.MeteredDistributedLockPort"; + + private static final DescribedPredicate ALLOWLISTED = + new DescribedPredicate<>("allowlisted from the composition-root boundary") { + @Override + public boolean test(JavaClass candidate) { + String name = candidate.getFullName(); + return name.equals(METERED_DISTRIBUTED_LOCK_PORT) + || name.startsWith(METERED_DISTRIBUTED_LOCK_PORT + "$"); + } + }; + + @ArchTest + static final ArchRule COMPOSITION_ROOT_DOES_NOT_IMPLEMENT_APPLICATION_OR_DOMAIN_PORTS = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.bootstrap..") + .and(DescribedPredicate.not(ALLOWLISTED)) + .should() + .implement( + JavaClass.Predicates.resideInAnyPackage( + "dev.caskeleton.application..", "dev.caskeleton.domain..")) + .as( + "BOOT-017: the composition root wires adapters, it does not implement application or " + + "domain ports. A class implementing one is an outbound adapter and belongs in " + + "an adapter/outbound/* leaf — see LoggingNotificationAudit and " + + "MicrometerNotificationMetrics in adapter/outbound/notification for the shape.") + .allowEmptyShould(true); +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java index 555bc3c4..7718fbc7 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainFeatureOnboardingContractTest.java @@ -31,27 +31,29 @@ class DomainFeatureOnboardingContractTest { ROOT + ".application.port.ListFeatureAggregatesWritePort"); assertThat( - CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE + ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_END_WITH_USE_CASE .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY + ApplicationBoundaryArchitectureTest.INBOUND_PORT_IMPLEMENTATIONS_DECLARE_CAPABILITY .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS + ApplicationBoundaryArchitectureTest + .READ_ONLY_USE_CASES_DO_NOT_CALL_REPOSITORY_WRITE_METHODS .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY + ApplicationBoundaryArchitectureTest + .USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES + TypeBoundaryArchitectureTest.CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); @@ -75,30 +77,33 @@ class DomainFeatureOnboardingContractTest { ROOT + ".adapter.inbound.web.dto.FeatureAggregateResponse", ROOT + ".adapter.inbound.web.controller.FeatureAggregateController"); - assertThat(CleanArchitectureTest.DOMAIN_IS_PURE.evaluate(ONBOARDING_CLASSES).hasViolation()) + assertThat( + DomainPurityArchitectureTest.DOMAIN_IS_PURE.evaluate(ONBOARDING_CLASSES).hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT + ApplicationBoundaryArchitectureTest.APPLICATION_DOES_NOT_DEPEND_ON_ADAPTERS_OR_TRANSPORT .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION + ApplicationBoundaryArchitectureTest.MUTATING_USE_CASES_DECLARE_REQUIRED_PERMISSION .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY + ApplicationBoundaryArchitectureTest + .USE_CASE_CAPABILITY_MATCHES_TRANSACTION_PORT_BOUNDARY .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS + AdapterBoundaryArchitectureTest + .PERSISTENCE_ADAPTER_DOES_NOT_DEPEND_ON_WEB_OR_OUTBOUND_ADAPTERS .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); assertThat( - CleanArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO + TypeBoundaryArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO .evaluate(ONBOARDING_CLASSES) .hasViolation()) .isFalse(); diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java new file mode 100644 index 00000000..8b7a27ac --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/DomainPurityArchitectureTest.java @@ -0,0 +1,250 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaConstructor; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaModifier; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import dev.caskeleton.domain.stereotype.AggregateRoot; +import dev.caskeleton.domain.stereotype.DomainEvent; +import dev.caskeleton.domain.stereotype.ValueObject; +import java.util.Locale; +import java.util.Set; + +/** + * The domain model's own rules: framework purity, the shape of value objects and aggregates, and + * the transport-neutrality of events. Nothing here reads a package outside {@code domain-core}. + * + *

도메인 모델 자체의 규칙 — 프레임워크 순수성, 값 객체와 애그리게이트의 형태, 이벤트의 전송 중립성. + * + *

{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by + * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields + * inside a nested class — the rules would have stopped running while the build stayed green + * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share + * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest} + * holds the full rule inventory so none can be lost in a move like this one. + */ +@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class) +class DomainPurityArchitectureTest { + + @ArchTest + static final ArchRule DOMAIN_IS_PURE = + noClasses() + .that() + .resideInAPackage("..domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "org.springframework..", + "jakarta.persistence..", + "javax.persistence..", + "jakarta.servlet..", + "org.hibernate..", + "lombok..", + "..application..", + "..adapter..", + "..bootstrap..", + "..service..", + "..infra..", + "..presentation..", + "..cmd..") + .as( + "domain-core must be framework-neutral POJO — no Spring / JPA / " + + "Hibernate / Lombok generated bytecode " + + "(feature-architecture-enforcement-rules D3)") + .allowEmptyShould(true); + + // ---- feature-domain-modeling-guardrails (modeling-specific rules; owner = this branch) ---- + // Domain framework-neutrality is delegated to DOMAIN_IS_PURE above + // (feature-architecture-enforcement-rules D3). The rules below add the + // modeling-specific guardrails this branch owns: logger ban (D3), value-object + // invariant surface (D5/D6), aggregate mutator visibility (D7), and transport-free + // domain events (D4/D8). They key off the dev.caskeleton.domain.stereotype markers. + + @ArchTest + static final ArchRule DOMAIN_HAS_NO_LOGGER = + noClasses() + .that() + .resideInAPackage("..domain..") + .should() + .dependOnClassesThat() + .resideInAnyPackage( + "org.slf4j..", + "java.util.logging..", + "ch.qos.logback..", + "org.apache.logging.log4j..") + .as( + "D3 DOMAIN_HAS_NO_LOGGER: the domain layer must not depend on a logging " + + "framework (SLF4J / java.util.logging / Logback / Log4j). An invariant " + + "violation is expressed as a domain exception carrying a safe reason " + + "value (noun enum); the application layer translates that into a log " + + "line. This is a ca-tmpl convention (UNSUPPORTED_DECISION — no official " + + "standard source), kept SEPARATE from DOMAIN_IS_PURE to preserve the " + + "owner boundary of feature-architecture-enforcement-rules D3 " + + "(feature-domain-modeling-guardrails D3, §2)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR = + classes() + .that() + .areAnnotatedWith(ValueObject.class) + .or() + .resideInAPackage("..domain.vo..") + .should(notHaveAPublicNoArgConstructor()) + .as( + "D5/D6 VALUE_OBJECTS_HAVE_NO_PUBLIC_NO_ARG_CONSTRUCTOR: a @ValueObject " + + "(or any type under ..domain.vo..) must validate its invariant on the " + + "sole construction path and must NOT expose a public no-arg constructor " + + "— an empty constructor is an invariant-bypassing back door. Records " + + "with components satisfy this automatically " + + "(feature-domain-modeling-guardrails D5/D6, §3)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC = + methods() + .that() + .haveNameMatching("set.*") + .and() + .areDeclaredInClassesThat() + .areAnnotatedWith(AggregateRoot.class) + .should() + .notBePublic() + .as( + "D7 AGGREGATE_ROOT_SETTERS_ARE_NOT_PUBLIC: a @AggregateRoot mutates its " + + "state only through intention-revealing methods that enforce invariants " + + "— a raw set* mutator must be package-private/protected (the visibility " + + "Vernon Option A ORM-external reconstruction relies on). ArchUnit only " + + "statically reaches the set* surface; other state-changing methods stay " + + "a code-review concern (feature-domain-modeling-guardrails D7, §4)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule DOMAIN_EVENTS_ARE_RECORDS = + classes() + .that() + .areAnnotatedWith(DomainEvent.class) + .should(beRecordTypes()) + .as( + "D4/D8 DOMAIN_EVENTS_ARE_RECORDS: a @DomainEvent is an immutable fact and " + + "must be declared as a record (feature-domain-modeling-guardrails D4/D8, §5)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule DOMAIN_EVENTS_ARE_TRANSPORT_FREE = + noClasses() + .that() + .areAnnotatedWith(DomainEvent.class) + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.apache.kafka..", "org.springframework.http..", "jakarta.ws.rs..") + .as( + "D4/D8 DOMAIN_EVENTS_ARE_TRANSPORT_FREE: a @DomainEvent carries domain data " + + "only and must not depend on a broker/wire/HTTP type (Kafka SDK, Spring " + + "HTTP, JAX-RS). Translating a domain event into an integration/wire event " + + "is the application/infrastructure boundary's job. UNSUPPORTED_IMPL_DECISION: " + + "the forbidden list enumerates only the SDKs ca-tmpl could plausibly add — " + + "extend it when a new broker/transport is introduced " + + "(feature-domain-modeling-guardrails D4/D8, §5)") + .allowEmptyShould(true); + + // ---- feature-persistence-auditing-contract D2 (domain stays free of audit metadata) ---- + // DOMAIN_IS_PURE already bans jakarta.persistence.. / org.springframework.. (incl. + // org.springframework.data..) from ..domain.., so @CreatedDate / @Column / AuditableEntity + // cannot reach the domain. This rule adds the audit-specific guardrail D2 owns: even as + // plain POJO fields, a domain aggregate must carry ZERO audit metadata — who/when + // created/modified is an adapter-persistence concern (AuditableEntity @MappedSuperclass), + // and putting it in the domain would invert the dependency (the domain would know "who + // logged in") and leak the infrastructure concern inward. + @ArchTest + static final ArchRule DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS = + noClasses() + .that() + .resideInAPackage("..domain..") + .should(declareAnAuditField()) + .as( + "D2 DOMAIN_ENTITIES_DO_NOT_CARRY_AUDIT_FIELDS: a domain class must not declare " + + "an audit field (createdAt / updatedAt / createdBy / updatedBy) — audit " + + "metadata lives only on the adapter-persistence AuditableEntity " + + "@MappedSuperclass so the domain aggregate carries none of it " + + "(feature-persistence-auditing-contract D2)") + .allowEmptyShould(true); + + // ---- feature-domain-modeling-guardrails custom conditions ---- + + private static final Set AUDIT_FIELD_NAMES = + Set.of("createdat", "updatedat", "createdby", "updatedby"); + + private static ArchCondition declareAnAuditField() { + return new ArchCondition<>( + "declare an audit field " + "(createdAt / updatedAt / createdBy / updatedBy)") { + @Override + public void check(JavaClass item, ConditionEvents events) { + for (JavaField field : item.getFields()) { + if (AUDIT_FIELD_NAMES.contains(field.getName().toLowerCase(Locale.ROOT))) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " declares audit field '" + + field.getName() + + "' — audit metadata must live only on the adapter-persistence " + + "AuditableEntity @MappedSuperclass, not on a domain class " + + "(feature-persistence-auditing-contract D2)")); + } + } + } + }; + } + + private static ArchCondition notHaveAPublicNoArgConstructor() { + return new ArchCondition<>("not have a public no-arg constructor") { + @Override + public void check(JavaClass item, ConditionEvents events) { + for (JavaConstructor constructor : item.getConstructors()) { + if (constructor.getRawParameterTypes().isEmpty() + && constructor.getModifiers().contains(JavaModifier.PUBLIC)) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " exposes a public no-arg constructor — " + + "a value object must validate its invariant on the sole " + + "construction path and offer no empty back door " + + "(feature-domain-modeling-guardrails D5/D6)")); + } + } + } + }; + } + + private static ArchCondition beRecordTypes() { + return new ArchCondition<>("be a record") { + @Override + public void check(JavaClass item, ConditionEvents events) { + if (!item.isRecord()) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " is annotated @DomainEvent but is not a " + + "record — a domain event is an immutable fact and must be a " + + "record (feature-domain-modeling-guardrails D4/D8)")); + } + } + }; + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java new file mode 100644 index 00000000..d6226bb6 --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/ForbiddenApiArchitectureTest.java @@ -0,0 +1,663 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaAnnotation; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import dev.caskeleton.domain.identifier.ResourceId; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Pattern; +import org.hibernate.type.SqlTypes; + +/** + * Bans on specific APIs and idioms. Each entry is one incident rather than a principle: a {@code + * new BigDecimal(double)} that lost money, an {@code InheritableThreadLocal} that leaked across a + * pooled thread, a {@code ProblemDetail} that bypassed the error envelope. They are grouped by + * being unrelated to each other — which is exactly why they were unreadable inside a 2,792-line + * class. + * + *

개별 API·관용구 금지 규칙 — 각각이 한 번의 사고에서 나온 항목이다. + * + *

{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by + * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields + * inside a nested class — the rules would have stopped running while the build stayed green + * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share + * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest} + * holds the full rule inventory so none can be lost in a move like this one. + */ +@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class) +class ForbiddenApiArchitectureTest { + + private static ArchCondition resideInAllowedSharedContractPackage() { + return new ArchCondition<>("reside in an allowed shared operational contract package") { + private final Set allowedPackages = + Set.of( + "dev.caskeleton.shared", + "dev.caskeleton.shared.response", + "dev.caskeleton.shared.request", + "dev.caskeleton.shared.error", + "dev.caskeleton.shared.operation", + "dev.caskeleton.shared.headers", + "dev.caskeleton.shared.logging", + "dev.caskeleton.shared.tracing", + "dev.caskeleton.shared.metrics", + "dev.caskeleton.shared.registry", + "dev.caskeleton.shared.annotation", + // Not this module at all — see the defect note on the rule above. The Mongo leaf has + // a + // tenancy.shared package of its own and only began tripping this rule when that leaf + // joined the composition root's analysis scope. + "dev.caskeleton.adapter.outbound.mongo.advanced.tenancy.shared", + // The five master switches and their strict parse. Operational rather than domain: + // it names environment keys and rejects unreadable values, and it lives here because + // the environment post-processor needs it before a Spring context exists, so it must + // not depend on Spring. + "dev.caskeleton.shared.activation", + // feature-authentication-authorization-contract: the Permission + // (resource:action) value object is a Java-only operational + // authorization contract consumed by AuthorizationPort. + "dev.caskeleton.shared.security", + // feature-runtime-context-propagation-contract: the domain-context + // propagation port + default ThreadLocal strategy + factory + // (swappable abstraction, the rate-limit pattern). + "dev.caskeleton.shared.concurrency"); + + @Override + public void check(JavaClass item, ConditionEvents events) { + String packageName = item.getPackageName(); + boolean allowed = false; + for (String allowedPackage : allowedPackages) { + if (packageName.equals(allowedPackage) || packageName.startsWith(allowedPackage + ".")) { + allowed = true; + break; + } + } + if (!allowed) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " resides in " + + packageName + + ", which is not an allowed shared operational contract package")); + } + } + }; + } + + @ArchTest + // KNOWN DEFECT, deliberately left as found. Two things are wrong and they mask each other: the + // subject pattern "..shared.." CleanArchitectureRuleSupport.matches any package named shared + // anywhere rather than this module, + // and the allowlist below holds the bare module root while the check + // CleanArchitectureRuleSupport.matches by prefix — so every + // package inside dev.caskeleton.shared is blessed automatically. The rule therefore catches + // nothing in the module it is named after, and does catch unrelated leaves that happen to have a + // "shared" package. Fixing it needs the violation fixture to move inside dev.caskeleton.shared, + // which puts it in this rule's own analysis scope, which needs the fixture-scanning structure + // reworked. That is a separate change; see the Wave 1 progress note. + static final ArchRule SHARED_CONTRACT_CONTAINS_ONLY_OPERATIONAL_CONTRACT_PACKAGES = + classes() + .that() + .resideInAPackage("..shared..") + .should(resideInAllowedSharedContractPackage()) + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_JACKSON_LAISSEZ_FAIRE_SUBTYPE_VALIDATOR = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName( + "com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator") + .as( + "LaissezFaireSubTypeValidator allows any subtype during polymorphic " + + "deserialization and is the CVE-2019-14379 RCE entry point " + + "(feature-boundary-validation-mapping-contract B5)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_JACKSON_ENABLE_DEFAULT_TYPING_CALL = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .callMethodWhere(target(name("enableDefaultTyping"))) + .as( + "ObjectMapper.enableDefaultTyping() (deprecated since Jackson 2.10) opens " + + "polymorphic deserialization to all classes — the documented " + + "CVE-2019-14379 RCE vector (feature-boundary-validation-mapping-contract B5). " + + "Use @JsonTypeInfo(use = NAME) + @JsonSubTypes, or " + + "activateDefaultTyping(BasicPolymorphicTypeValidator) with an explicit " + + "allowlist — the LaissezFaireSubTypeValidator class reference rule " + + "still blocks the unsafe variant.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_INHERITABLE_THREAD_LOCAL = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName("java.lang.InheritableThreadLocal") + .as( + "InheritableThreadLocal does not propagate cleanly to virtual threads " + + "and leaks context across pooled carrier threads — use MDC (SLF4J " + + "2.0+) or RequestContextHolder instead " + + "(feature-boundary-validation-mapping-contract B6)") + .allowEmptyShould(true); + + // ---- feature-runtime-context-propagation-contract (domain extension of B6) ---- + // Strategy guard for the domain-context propagation seam. The shipped default is + // ThreadLocal (DomainContextStrategy.THREAD_LOCAL — plain ThreadLocal, no preview + // API); the MICROMETER / SCOPED_VALUE strategies are reserved-but-unshipped. This + // rule keeps the two Java 21 PREVIEW primitives (ScopedValue / StructuredTaskScope) + // out of production code until the SCOPED_VALUE strategy is actually activated + // through the factory seam. The build has no --enable-preview (build fact C1), so + // those APIs cannot even compile today — the compile gate is the first line of + // defence and is why a compilable violation fixture is infeasible; this rule is the + // dormant defence-in-depth that fires the moment --enable-preview is enabled and + // someone reaches for the preview primitive without adding the strategy + factory + // case. It is NOT the deferred S4/D7 activation-correctness rule + // (UNSUPPORTED_IMPL_DECISION — rule shape intentionally not invented). The + // implicit-inheritance ban (S3, the ThreadLocal default's contract) cross-cites + // NO_INHERITABLE_THREAD_LOCAL above (owner: boundary-validation B6). + + @ArchTest + static final ArchRule DOMAIN_CONTEXT_PROPAGATION_PRIMITIVES_STAY_UNSHIPPED = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .haveNameMatching( + "(java\\.lang\\.ScopedValue" + + "|java\\.util\\.concurrent\\.StructuredTaskScope)(\\$.*)?") + .as( + "the domain-context default is ThreadLocal; the ScopedValue / " + + "StructuredTaskScope (preview) strategy must not appear in production " + + "code until activated through the DomainContextStrategy + factory seam " + + "(feature-runtime-context-propagation-contract S2)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_PROBLEM_DETAIL_USAGE = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName("org.springframework.http.ProblemDetail") + .as( + "D5: RFC 7807 ProblemDetail is explicitly rejected — error responses must use " + + "the shared Envelope shape " + + "(feature-boundary-validation-mapping-contract D5). Production code is " + + "scanned only — test fixtures in ..architecture.violations.. are out of " + + "scope under ImportOption.DoNotIncludeTests.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_MERGE_PATCH_JSON_MEDIA_TYPE_STRING = + classes() + .that() + .resideInAPackage("dev.caskeleton..") + .should(notReferenceMediaTypeString("application/merge-patch+json")) + .as( + "B2: RFC 7396 application/merge-patch+json content type is explicitly rejected " + + "— PATCH endpoints must use JSON with JsonNullable 3-state " + + "(feature-boundary-validation-mapping-contract B2)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule + MESSAGING_OUTBOUND_PUBLIC_INSTANCE_METHODS_DO_NOT_LEAK_ADAPTER_TYPES_THROUGH_GENERICS = + methods() + .that() + .areDeclaredInClassesThat() + .resideInAPackage("..adapter.outbound.messaging..") + .and() + .areDeclaredInClassesThat() + .areNotAnnotatedWith("org.springframework.context.annotation.Configuration") + .and() + .areDeclaredInClassesThat() + .areNotAnnotatedWith( + "org.springframework.boot.context.properties.ConfigurationProperties") + .and() + .arePublic() + .and() + .areNotStatic() + .should( + notReturnAdapterTypesIncludingGenericArguments( + "..adapter.outbound..", + "..adapter.inbound.web..", + "..adapter.outbound.persistence..")) + .as( + "B7 messaging hardening: every public non-static messaging method must keep " + + "adapter-local types out of direct and recursive generic return positions; " + + "narrow public static composition bridges are the only allowed adapter-local " + + "return seam") + .allowEmptyShould(true); + + private static ArchCondition notReturnAdapterTypesIncludingGenericArguments( + String... forbiddenPackages) { + DescribedPredicate forbidden = + JavaClass.Predicates.resideInAnyPackage(forbiddenPackages); + return new ArchCondition<>( + "not return adapter-local types directly or through generic arguments") { + @Override + public void check(JavaMethod method, ConditionEvents events) { + for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) { + if (forbidden.test(involved)) { + events.add( + SimpleConditionEvent.violated( + method, + "Method " + + method.getFullName() + + " leaks " + + involved.getName() + + " through its return type (directly or as a generic argument)" + + " — B7 permits only narrow static composition bridges for " + + "adapter-local types")); + } + } + } + }; + } + + // ---- feature-api-contract-baseline D19 (resource URL naming, AIP-122) ---- + + @ArchTest + static final ArchRule CONTROLLER_REQUEST_MAPPINGS_FOLLOW_AIP122 = + classes() + .that() + .resideInAPackage("..adapter.inbound.web..controller..") + .should(haveAip122CompliantRequestMappingPaths()) + .as( + "D19: controller @RequestMapping path segments must follow AIP-122 — lowercase " + + "first char + ASCII alphanumerics ([a-z][a-zA-Z0-9]*), so kebab-case " + + "(/work-logs), CamelCase (/Tickets) and SNAKE_CASE are rejected. " + + "{pathVariables} and AIP-136 colon-verb custom methods " + + "(/worklogs:batchCreate) are allowed (feature-api-contract-baseline D19)") + .allowEmptyShould(true); + + private static final Pattern AIP122_SEGMENT = Pattern.compile("[a-z][a-zA-Z0-9]*"); + private static final Pattern AIP136_VERB = Pattern.compile("[a-zA-Z][a-zA-Z0-9]*"); + private static final Set MAPPING_ANNOTATION_TYPES = + Set.of( + "org.springframework.web.bind.annotation.RequestMapping", + "org.springframework.web.bind.annotation.GetMapping", + "org.springframework.web.bind.annotation.PostMapping", + "org.springframework.web.bind.annotation.PutMapping", + "org.springframework.web.bind.annotation.PatchMapping", + "org.springframework.web.bind.annotation.DeleteMapping"); + + private static ArchCondition haveAip122CompliantRequestMappingPaths() { + return new ArchCondition<>("have AIP-122 compliant @RequestMapping path segments") { + @Override + public void check(JavaClass item, ConditionEvents events) { + checkMappingPaths(item, item.getAnnotations(), events); + for (JavaMethod m : item.getMethods()) { + checkMappingPaths(item, m.getAnnotations(), events); + } + } + }; + } + + private static void checkMappingPaths( + JavaClass owner, Set> annotations, ConditionEvents events) { + for (JavaAnnotation annotation : annotations) { + if (!MAPPING_ANNOTATION_TYPES.contains(annotation.getRawType().getName())) { + continue; + } + for (String key : List.of("value", "path")) { + annotation + .get(key) + .ifPresent( + value -> { + for (String path : toPathStrings(value)) { + for (String segment : path.split("/", 0)) { + validatePathSegment(owner, segment, path, events); + } + } + }); + } + } + } + + private static List toPathStrings(Object value) { + if (value instanceof String s) { + return List.of(s); + } + if (value instanceof Object[] arr) { + List out = new ArrayList<>(); + for (Object e : arr) { + if (e != null) { + out.add(e.toString()); + } + } + return out; + } + return List.of(); + } + + private static void validatePathSegment( + JavaClass owner, String segment, String path, ConditionEvents events) { + if (segment.isEmpty() || (segment.startsWith("{") && segment.endsWith("}"))) { + return; // empty segment or {pathVariable} + } + String[] colonParts = segment.split(":", 2); + String resource = colonParts[0]; + if (!resource.isEmpty() && !AIP122_SEGMENT.matcher(resource).matches()) { + events.add( + SimpleConditionEvent.violated( + owner, + "Class " + + owner.getName() + + " maps non-AIP-122 path segment '" + + segment + + "' in '" + + path + + "' — use lowercase [a-z][a-zA-Z0-9]* (no kebab/Camel/snake)")); + return; + } + if (colonParts.length == 2 && !AIP136_VERB.matcher(colonParts[1]).matches()) { + events.add( + SimpleConditionEvent.violated( + owner, + "Class " + + owner.getName() + + " maps non-AIP-136 colon-verb '" + + segment + + "' in '" + + path + + "'")); + } + } + + private static ArchCondition notReferenceMediaTypeString(String mediaType) { + return new ArchCondition<>("not reference media type string '" + mediaType + "'") { + @Override + public void check(JavaClass item, ConditionEvents events) { + // The realistic carriers — @RequestMapping(consumes=...), + // @PostMapping(consumes=...), and so on — sit on classes AND methods. + // Bytecode does not retain String initializer values for non-final + // fields, so free-form references inside method bodies cannot be + // detected at the ArchUnit level. We catch the annotation carriers + // (class and method) which is where the realistic usage lives. + scanAnnotations(item, item.getAnnotations(), mediaType, events); + for (JavaMethod m : item.getMethods()) { + scanAnnotations(item, m.getAnnotations(), mediaType, events); + } + } + }; + } + + private static void scanAnnotations( + JavaClass owner, + Set> annotations, + String mediaType, + ConditionEvents events) { + for (JavaAnnotation annotation : annotations) { + // Annotation properties on Spring's @RequestMapping family carry their values as + // String[] which boxes to Object[]. Some ArchUnit versions also expose property + // values as plain String. Walk both shapes and also fall back to the rendered + // annotation text so single-value carriers (e.g. MediaType constants resolved at + // bytecode level) are caught. + for (Object v : annotation.getProperties().values()) { + if (CleanArchitectureRuleSupport.matches(v, mediaType)) { + events.add( + SimpleConditionEvent.violated( + owner, + "Class " + + owner.getName() + + " uses '" + + mediaType + + "' via @" + + annotation.getRawType().getSimpleName())); + return; + } + } + if (annotation.toString().contains(mediaType)) { + events.add( + SimpleConditionEvent.violated( + owner, + "Class " + + owner.getName() + + " uses '" + + mediaType + + "' (detected in @" + + annotation.getRawType().getSimpleName() + + " text)")); + } + } + } + + // ---- feature-resource-identifier-contract D17 (decision SSOT: resource-identifier branch) ---- + + @ArchTest + static final ArchRule NO_LONG_ID_PK = + fields() + .that() + .areDeclaredInClassesThat() + .resideInAPackage("..domain..") + .and() + .haveNameMatching("id") + .should() + .haveRawType(assignableTo(ResourceId.class)) + .as( + "D17 NO_LONG_ID_PK: a domain entity 'id' field must be a ResourceId value object" + + " (e.g. WorkLogId), never Long/long/int/Integer" + + " (feature-resource-identifier-contract D17). JPA @Id UUID columns in" + + " ..adapter.outbound.persistence.. are out of scope — they store the UUID as" + + " the PostgreSQL native uuid type per D10.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_UUID_RANDOM_IN_CONTROLLER = + noClasses() + .that() + .resideInAnyPackage("..adapter.inbound.web..controller..", "..application..") + .should() + .callMethod(UUID.class, "randomUUID") + .orShould() + .dependOnClassesThat() + .haveFullyQualifiedName("com.github.f4b6a3.uuid.UuidCreator") + .as( + "D5/D17 NO_UUID_RANDOM_IN_CONTROLLER: controllers and the application layer " + + "(use cases / services) must not generate resource identifiers directly " + + "(UUID.randomUUID / UuidCreator) — inject the domain WorkLogIdFactory port " + + "instead (feature-resource-identifier-contract D5/D17). Scope follows the " + + "D17 decision text (controller / service / use case); request/trace-id " + + "generation in web filters is a distinct concern owned by D18 / " + + "feature-distributed-tracing-contract and is intentionally out of scope.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_MATH_RANDOM_FOR_ID = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .callMethod(Math.class, "random") + .as( + "D9/D17 NO_MATH_RANDOM_FOR_ID: Math.random() is not a CSPRNG and must never " + + "seed an identifier (feature-resource-identifier-contract D9/D17).") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_VARCHAR_255_FOR_ID_COLUMN = + fields() + .that() + .areAnnotatedWith("jakarta.persistence.Column") + .and() + .haveNameMatching(".*[iI]d") + .should(haveExplicitIdColumnMapping()) + .as( + "D17 NO_VARCHAR_255_FOR_ID_COLUMN: an @Column-mapped id field must declare an " + + "explicit non-default length or a portable UUID type hint — never fall " + + "back to varchar(255) (feature-resource-identifier-contract D17).") + .allowEmptyShould(true); + + // ---- feature-streaming-response-contract D3 (event/server-push streaming ban) ---- + + @ArchTest + static final ArchRule NO_SSE_EMITTER = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName( + "org.springframework.web.servlet.mvc.method.annotation.SseEmitter") + .as( + "D3 NO_SSE_EMITTER: production code must not import SseEmitter — " + + "SSE server-push streaming (W3C EventSource / text/event-stream) is " + + "not supported in ca-skeleton (feature-streaming-response-contract D3). " + + "Production code is scanned only — test fixtures in " + + "..architecture.violations.. are out of scope under " + + "ImportOption.DoNotIncludeTests.") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_RESPONSE_BODY_EMITTER = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .haveFullyQualifiedName( + "org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter") + .as( + "D3 NO_RESPONSE_BODY_EMITTER: production code must not import " + + "ResponseBodyEmitter — it is the SSE base class and incremental " + + "object-emit surface (SPRING-ASYNC-C3) treated as server-push " + + "(UNSUPPORTED_IMPL_DECISION④). Note: StreamingResponseBody " + + "(large-file download, request-response model) is NOT blocked — " + + "it is owned by feature-file-resource-handling-contract D8 " + + "(feature-streaming-response-contract D3).") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule NO_WEBSOCKET_HANDLER = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .dependOnClassesThat() + .resideInAnyPackage("org.springframework.web.socket..", "jakarta.websocket..") + .as( + "D3 NO_WEBSOCKET_HANDLER: production code must not depend on " + + "org.springframework.web.socket.. or jakarta.websocket.. — " + + "these cover the spring-websocket handler/STOMP surfaces and " + + "Jakarta @ServerEndpoint (RFC 6455 full-duplex WebSocket model). " + + "Full-duplex streaming is not supported in ca-skeleton " + + "(feature-streaming-response-contract D3). Production code is " + + "scanned only under ImportOption.DoNotIncludeTests.") + .allowEmptyShould(true); + + // ---- feature-secrets-config-source-contract D3/D10 (no-runtime-reload static enforcement) ---- + + @ArchTest + static final ArchRule NO_REFRESH_SCOPE_ANYWHERE = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .beAnnotatedWith("org.springframework.cloud.context.config.annotation.RefreshScope") + .as( + "D3/D10 NO_REFRESH_SCOPE_ANYWHERE: secrets default to no-runtime-reload " + + "(rotation = restart validation), so @RefreshScope is banned across the " + + "whole production tree — it is the in-process config-reload machinery the " + + "VAULT-DYN-C2 dynamic-reload rejection forbids " + + "(feature-secrets-config-source-contract D3/D10). The ban is total (no " + + "carve-out): an explicit rotation handler (e.g. JWT 24h overlap, owned by " + + "another branch) rotates via a mutable holder + scheduled swap, never " + + "@RefreshScope. spring-cloud-context is not on the production classpath, so " + + "the annotation is referenced by fully-qualified name. Production code is " + + "scanned only — the fixture in ..architecture.violations.secrets.. is out " + + "of scope under ImportOption.DoNotIncludeTests.") + .allowEmptyShould(true); + + // ---- feature-schema-serialization-contract D3 (BigDecimal precision trap ban) ---- + + @ArchTest + static final ArchRule NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .should() + .callConstructor(BigDecimal.class, double.class) + .orShould() + .callConstructor(BigDecimal.class, float.class) + .as( + "D3 NO_BIGDECIMAL_DOUBLE_CONSTRUCTOR: new BigDecimal(double) / " + + "new BigDecimal(float) captures the binary floating-point error " + + "(new BigDecimal(0.1) == 0.1000000000000000055...) instead of the exact " + + "decimal — money/decimal values must be built from a String " + + "(new BigDecimal(\"0.1\") or BigDecimal.valueOf(double)) " + + "(feature-schema-serialization-contract D3 / SBMS-C3). Production code is " + + "scanned only — test fixtures in ..architecture.violations.. are out of " + + "scope under ImportOption.DoNotIncludeTests.") + .allowEmptyShould(true); + + private static ArchCondition haveExplicitIdColumnMapping() { + return new ArchCondition<>( + "declare an explicit non-default length or portable UUID type hint") { + @Override + public void check(JavaField field, ConditionEvents events) { + field + .tryGetAnnotationOfType("jakarta.persistence.Column") + .ifPresent( + column -> { + int length = column.get("length").map(v -> ((Number) v).intValue()).orElse(255); + boolean explicit = length != 255 || hasPortableUuidTypeHint(field); + if (!explicit) { + events.add( + SimpleConditionEvent.violated( + field, + "Field " + + field.getFullName() + + " maps an id column with the implicit " + + "varchar(255) default — declare an explicit length or " + + "@JdbcTypeCode(SqlTypes.UUID) for UUID fields " + + "(feature-resource-identifier-contract D17)")); + } + }); + } + }; + } + + private static boolean hasPortableUuidTypeHint(JavaField field) { + if (!field.getRawType().isEquivalentTo(UUID.class)) { + return false; + } + return field + .tryGetAnnotationOfType("org.hibernate.annotations.JdbcTypeCode") + .flatMap(annotation -> annotation.get("value")) + .filter(value -> value instanceof Number number && number.intValue() == SqlTypes.UUID) + .isPresent(); + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java new file mode 100644 index 00000000..d8911db9 --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/StartupFailureContractArchitectureTest.java @@ -0,0 +1,139 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; +import java.util.List; + +/** + * The composition root's startup failures must carry an exit code (BOOT-003). + * + *

{@code StartupErrorCode} classifies a failed start into four causes and gives each one a + * sysexits exit status — 78 for a configuration error, 70 for a failed migration, 71 for a profile + * conflict, 72 for a required adapter that is off. That status only reaches the JVM because {@code + * StartupFailureException} implements {@code ExitCodeGenerator}, and the structured failure log + * with {@code startup.phase} / {@code error.code} only exists because {@code StartupFailures} emits + * it before returning the exception. + * + *

None of that happens for {@code throw new IllegalStateException(...)}. This leaf held 65 + * places that constructed one; 60 of them were startup validation and went through none of the + * machinery above, so the process died with a generic exit 1, no {@code error.code} field, and + * Spring Boot's full "Application run failed" stacktrace instead of the one line an operator needs. + * The classification existed and roughly a third of the throws used it. + * + *

The allowlist

+ * + *

Three classes may still construct {@code IllegalStateException} for a reason of their own, and + * the distinction is not stylistic. {@code StartupFailures} reports an operator configured + * something wrong; these three report a caller or the JVM broke an invariant, which + * is not a deployment problem and must not be dressed as one — assigning it exit 78 would tell an + * operator to go and fix a configuration file that is perfectly correct. The remaining two entries + * are the failure machinery itself. + * + *

{@code IllegalArgumentException} is deliberately not covered. Rejecting a bad method argument + * is a different act from rejecting a bad deployment, and giving it an exit code would say the + * deployment was misconfigured when a caller passed the wrong value. + * + *

The allowlist is spelled out here rather than expressed as a package or naming pattern + * precisely so that it cannot grow quietly: adding a class to it is a visible edit to this file + * with a reviewer attached. + */ +@AnalyzeClasses( + packages = "dev.caskeleton.bootstrap", + importOptions = ProductionClassImportOption.class) +class StartupFailureContractArchitectureTest { + + /** + * {@code TracingSettings#sampleRateValue()} — an API precondition. Blank means "defer to the + * per-profile default"; calling the accessor anyway is a bug in the caller, not in configuration. + */ + private static final String TRACING_SETTINGS = + "dev.caskeleton.bootstrap.autoconfigure.tracing.TracingSettings"; + + /** + * {@code HttpClientEnvironmentKeys} — an internal invariant. Its unreachable branch says the + * environment-variable template cannot express a list nested three deep; no settings record nests + * that far, and a deployment cannot cause it. + */ + private static final String HTTP_CLIENT_ENVIRONMENT_KEYS = + "dev.caskeleton.bootstrap.autoconfigure.httpclient.HttpClientEnvironmentKeys"; + + /** + * {@code RedisKeys} — two JCE invariants ("HmacSHA256 must be available", "a Mac must be + * cloneable"). A JVM without HmacSHA256 is not a misconfigured deployment. + * + *

This entry named {@code RedisCapabilityConfig} until the Redis capability was split by + * concern (BOOT-007) and the key digest moved to a type of its own. The rule caught the move, + * which is the behaviour to want from it: an allowlist that silently followed a class around + * would stop being a statement about which code is exempt. + */ + private static final String REDIS_KEYS = "dev.caskeleton.bootstrap.autoconfigure.redis.RedisKeys"; + + /** + * {@code StartupFailures} itself — it constructs the typed exceptions this rule exists to + * require. + */ + private static final String STARTUP_FAILURES = + "dev.caskeleton.bootstrap.runtime.startup.StartupFailures"; + + /** + * {@code StartupFailureException} — the typed base class. Its constructor delegates to {@code + * IllegalStateException(String, Throwable)}, which is the whole point: the typed exceptions stay + * source-compatible with the {@code IllegalStateException} the startup path used to throw, so + * every existing {@code assertThatThrownBy(...).isInstanceOf(IllegalStateException.class)} kept + * passing across this migration. + */ + private static final String STARTUP_FAILURE_EXCEPTION = + "dev.caskeleton.bootstrap.runtime.startup.StartupFailureException"; + + /** + * Matched by prefix rather than exact name, so a nested type is covered by its outer class's + * entry. + * + *

Not cosmetic: the first version of this rule compared exact fully-qualified names and + * therefore did not cover {@code RedisCapabilityConfig$KeyDigest}, where two of the three + * allowlisted Redis throws actually live. The rule fired on them, and the obvious fix — adding + * the nested name too — would have had to be repeated for every future nested type. A prefix + * keeps the allowlist to one line per class while still naming that class explicitly. + */ + private static final DescribedPredicate ALLOWLISTED = + new DescribedPredicate<>("allowlisted from the StartupFailures contract") { + private final List prefixes = + List.of( + TRACING_SETTINGS, + HTTP_CLIENT_ENVIRONMENT_KEYS, + REDIS_KEYS, + STARTUP_FAILURES, + STARTUP_FAILURE_EXCEPTION); + + @Override + public boolean test(JavaClass candidate) { + String name = candidate.getFullName(); + return prefixes.stream() + .anyMatch(prefix -> name.equals(prefix) || name.startsWith(prefix + "$")); + } + }; + + @ArchTest + static final ArchRule STARTUP_FAILURES_ARE_RAISED_THROUGH_THE_TYPED_FACTORY = + noClasses() + .that() + .resideInAPackage("dev.caskeleton.bootstrap..") + .and(DescribedPredicate.not(ALLOWLISTED)) + .should() + .callConstructor(IllegalStateException.class, String.class) + .orShould() + .callConstructor(IllegalStateException.class, String.class, Throwable.class) + .as( + "BOOT-003: a startup failure in the composition root must be raised through " + + "StartupFailures (envValidation / profileMismatch / requiredAdapterDisabled / " + + "migrationFailed), so it carries a sysexits exit code, emits the structured " + + "startup.phase/error.code log, and is recognised by " + + "StartupFailureExceptionReporter. A raw IllegalStateException gets exit 1, no " + + "error code, and Spring Boot's generic stacktrace.") + .allowEmptyShould(true); +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java index c67b583d..48624ba6 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TestTaxonomyArchitectureTest.java @@ -3,6 +3,7 @@ package dev.caskeleton.bootstrap.architecture; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; import static org.assertj.core.api.Assertions.assertThat; +import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; import com.tngtech.archunit.lang.ArchRule; @@ -47,6 +48,16 @@ class TestTaxonomyArchitectureTest { private static final JavaClasses ARCHITECTURE_TESTS = new ClassFileImporter().importPackages("dev.caskeleton.bootstrap.architecture"); + /** + * Everything under {@code dev.caskeleton.bootstrap} the {@code test} JVM can see — production + * classes plus the whole {@code test} source set, and deliberately not the {@code + * integrationTest} source set, whose output is absent from the {@code test} runtime classpath. + * That absence is what makes this corpus a source-set boundary check rather than another package + * check (BOOT-014). + */ + private static final JavaClasses TEST_JVM_CORPUS = + new ClassFileImporter().importPackages("dev.caskeleton.bootstrap"); + // ------------------------------------------------------------------------- // Positive-control / over-block corpora — deterministic class-literal imports. // ------------------------------------------------------------------------- @@ -147,6 +158,117 @@ class TestTaxonomyArchitectureTest { .isTrue(); } + // ------------------------------------------------------------------------- + // BOOT-014 — the source-set boundary, not merely the package name + // ------------------------------------------------------------------------- + + /** + * ArchUnit rule: nothing reachable from the {@code test} source set may depend on Testcontainers. + * + *

The two rules above are scoped to the {@code ..contract..} and {@code ..architecture..} + * packages, so they constrain where a Testcontainers test may be *named* while saying nothing + * about which Gradle source set compiles it. That is exactly how {@code + * ..bootstrap.integration..} — nine Testcontainers classes, four of them with no + * {@code @EnabledIf} guard — sat inside {@code src/test} and made {@code ./gradlew + * :app-bootstrap:test} require a Docker daemon. + * + *

This rule closes that gap by constraining the corpus rather than a package: it evaluates + * everything under {@code dev.caskeleton.bootstrap} that the {@code test} JVM can see. The {@code + * integrationTest} source set's output is not on the {@code test} runtime classpath, so a class + * that moves back into {@code src/test} becomes visible here and fails, while the integration + * lane itself is invisible and unaffected. + * + *

{@code ..taxonomyfixtures..} is excluded because {@link + * dev.caskeleton.bootstrap.taxonomyfixtures.TestcontainersUsingFixture} carries its + * Testcontainers dependency deliberately, as the positive control proving the ban rules fire. + * Excluding it by package keeps the exception visible and narrow; the fixture's own non-vacuity + * test still asserts the dependency is really there. + */ + static final ArchRule TEST_SOURCE_SET_DOES_NOT_DEPEND_ON_TESTCONTAINERS = + noClasses() + .that() + .resideOutsideOfPackage("..taxonomyfixtures..") + .should() + .dependOnClassesThat() + .resideInAPackage("org.testcontainers..") + .as( + "BOOT-014: the `test` source set must not depend on Testcontainers — " + + "`./gradlew :app-bootstrap:test` must not require a Docker daemon. " + + "Real-service tests belong to src/integrationTest (the `integrationTest` lane).") + .allowEmptyShould(true); + + @Test + void testSourceSetHasNoTestcontainersDependency() { + // Non-vacuity guard: an empty corpus would make this clean-check pass without checking + // anything. + assertThat(TEST_JVM_CORPUS.size()) + .as( + "test-JVM corpus (dev.caskeleton.bootstrap) must be non-empty — an empty scan would " + + "make this clean-check pass vacuously (BOOT-014)") + .isGreaterThan(0); + + EvaluationResult result = + TEST_SOURCE_SET_DOES_NOT_DEPEND_ON_TESTCONTAINERS.evaluate(TEST_JVM_CORPUS); + + assertThat(result.hasViolation()) + .as( + "TEST_SOURCE_SET_DOES_NOT_DEPEND_ON_TESTCONTAINERS must NOT fire on the test source " + + "set. If it did, a Testcontainers-backed test was added to src/test instead of " + + "src/integrationTest — move it, do not widen this rule (BOOT-014). Violations: " + + result.getFailureReport()) + .isFalse(); + } + + /** + * The boundary rule with its fixture exclusion removed. + * + *

Exists only as the non-vacuity instrument for {@link + * #testSourceSetBoundaryIsNonVacuousAndItsExclusionIsLoadBearing()}. A positive control for the + * boundary rule cannot be a new Testcontainers-using fixture, because the clean-check above scans + * every package except {@code ..taxonomyfixtures..} and would flag it — the control would break + * the very rule it exists to prove. Dropping the exclusion instead turns the fixture that is + * already in the corpus into the control. + */ + private static final ArchRule TESTCONTAINERS_BAN_WITHOUT_FIXTURE_EXCLUSION = + noClasses() + .should() + .dependOnClassesThat() + .resideInAPackage("org.testcontainers..") + .allowEmptyShould(true); + + @Test + void testSourceSetBoundaryIsNonVacuousAndItsExclusionIsLoadBearing() { + // Detection proof. Without the ..taxonomyfixtures.. exclusion the identical predicate fires on + // this corpus, because TestcontainersUsingFixture is in it. So the clean-check passing above + // means the rule looked and found nothing — not that it was unable to look. + assertThat( + TESTCONTAINERS_BAN_WITHOUT_FIXTURE_EXCLUSION.evaluate(TEST_JVM_CORPUS).hasViolation()) + .as( + "the boundary rule's predicate must fire on the deliberate fixture once the " + + "..taxonomyfixtures.. exclusion is removed — otherwise the clean-check is " + + "vacuous and the exclusion is not load-bearing (BOOT-014)") + .isTrue(); + + // Scope proof. The corpus must reach test packages the two older package-scoped rules never + // cover, or this rule adds nothing over them and BOOT-014 could recur outside their reach. + assertThat(corpusCoversPackage(TEST_JVM_CORPUS, "dev.caskeleton.bootstrap.runtime")) + .as( + "test-JVM corpus must include ..bootstrap.runtime.. — a corpus limited to contract and " + + "architecture would duplicate the existing rules instead of closing the " + + "source-set gap (BOOT-014)") + .isTrue(); + } + + /** Reports whether the corpus holds at least one class in the given package tree. */ + private static boolean corpusCoversPackage(JavaClasses corpus, String packagePrefix) { + for (JavaClass candidate : corpus) { + if (candidate.getPackageName().startsWith(packagePrefix)) { + return true; + } + } + return false; + } + // ------------------------------------------------------------------------- // Task 3 — D7 / SB-SLICE-C2: Spring slice annotation mixing ban // ------------------------------------------------------------------------- @@ -203,11 +325,11 @@ class TestTaxonomyArchitectureTest { @Test void fixtureLeakRuleFiresOnProductionDependingOnFixture() { - // Evaluates the @ArchTest rule from CleanArchitectureTest against a deterministic + // Evaluates the @ArchTest rule from TypeBoundaryArchitectureTest against a deterministic // corpus: LeakyProductionConsumerFixture (outside ..fixtures..) depends on // LeakedTestFixture (in ..fixtures..). Proves the production-side guard fires. EvaluationResult result = - CleanArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES.evaluate( + TypeBoundaryArchitectureTest.PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES.evaluate( FIXTURE_LEAK_VIOLATION_CORPUS); assertThat(result.hasViolation()) diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java new file mode 100644 index 00000000..4177adf6 --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/TypeBoundaryArchitectureTest.java @@ -0,0 +1,532 @@ +package dev.caskeleton.bootstrap.architecture; + +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaAnnotation; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaField; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Whether a type escapes the layer that owns it. Dependency rules say which package may reference + * which; these say which types may appear in a signature, which is the leak that a + * dependency check cannot see — an allowed edge carrying a forbidden type across it. + * + *

타입이 자기 계층 밖으로 새지 않는가 — 엔티티, DTO, 도메인 타입의 이동 금지. + * + *

{@code CleanArchitectureTest} held all 75 of these in one 2,792-line class. It was split by + * concern rather than nested, because ArchUnit's engine does not discover {@code @ArchTest} fields + * inside a nested class — the rules would have stopped running while the build stayed green + * (BOOT-012). Separate top-level classes carrying the same {@code @AnalyzeClasses} arguments share + * the import cache, so the split costs no analysis time, and {@code ArchRuleDiscoveryContractTest} + * holds the full rule inventory so none can be lost in a move like this one. + */ +@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ProductionClassImportOption.class) +class TypeBoundaryArchitectureTest { + + /** A persistence entity is one the ORM maps, wherever it lives. */ + private static final DescribedPredicate ARE_PERSISTENCE_ENTITIES = + new DescribedPredicate<>("annotated as a persistence entity") { + @Override + public boolean test(JavaClass candidate) { + return candidate.isAnnotatedWith("jakarta.persistence.Entity") + || candidate.isAnnotatedWith("jakarta.persistence.MappedSuperclass") + || candidate.isAnnotatedWith("jakarta.persistence.Embeddable") + || candidate.isAnnotatedWith( + "org.springframework.data.mongodb.core.mapping.Document"); + } + }; + + /** A mapper is a translation type, identified by its name rather than its directory. */ + private static final DescribedPredicate ARE_PERSISTENCE_MAPPERS = + new DescribedPredicate<>("a persistence mapper by name") { + @Override + public boolean test(JavaClass candidate) { + return candidate.getSimpleName().endsWith("Mapper") + && candidate.getPackageName().contains(".adapter.outbound.persistence"); + } + }; + + @ArchTest + static final ArchRule PERSISTENCE_ENTITIES_STAY_INSIDE_A_PERSISTENCE_ADAPTER = + classes() + .that(ARE_PERSISTENCE_ENTITIES) + .should() + .resideInAnyPackage( + "..adapter.outbound.persistence..", + "..sample.portfolio.adapter.outbound.persistence..") + .as( + "a type the ORM maps belongs to a persistence adapter whatever it is called and " + + "wherever it is filed (NTF-022 semantic selection)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule WEB_DTOS_STAY_IN_WEB_ADAPTER = + classes() + .that() + .resideInAPackage("..adapter.inbound.web..dto..") + .should() + .onlyBeAccessed() + .byClassesThat() + .resideInAnyPackage("..adapter.inbound.web..") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule MAPPERS_DO_NOT_DEPEND_ON_WEB_OR_APPLICATION_BOUNDARIES = + noClasses() + .that(ARE_PERSISTENCE_MAPPERS) + .should() + .dependOnClassesThat() + .resideInAnyPackage("..adapter.inbound.web..", "..bootstrap..") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_SAMPLE_PORTFOLIO = + noClasses() + .that() + .resideOutsideOfPackage("..sample.portfolio..") + .should() + .dependOnClassesThat() + .resideInAPackage("..sample.portfolio.."); + + @ArchTest + static final ArchRule PRODUCTION_CODE_DOES_NOT_DEPEND_ON_TEST_FIXTURES = + noClasses() + .that() + .resideInAPackage("dev.caskeleton..") + .and() + .resideOutsideOfPackage("..fixtures..") + .should() + .dependOnClassesThat() + .resideInAPackage("..fixtures..") + .as( + "feature-test-taxonomy-fixture-contract D6: production code must never depend on a" + + " test fixture — fixtures live in src/test/.../fixtures/ (test-only); this" + + " guards against a fixture leaking onto the main classpath.") + .allowEmptyShould(true); + + // ---- feature-boundary-validation-mapping-contract ---- + + @ArchTest + static final ArchRule REQUEST_DTOS_DO_NOT_SILENCE_UNKNOWN_FIELDS = + classes() + .that() + .resideInAPackage("..adapter.inbound.web..dto..") + .should(notBeAnnotatedWithJsonIgnoreUnknown()) + .as( + "class-level @JsonIgnoreProperties(ignoreUnknown = true) on a request DTO " + + "silently swallows extra fields and defeats the B1 fail-fast boundary " + + "(feature-boundary-validation-mapping-contract B1)") + .allowEmptyShould(true); + + // ---- feature-boundary-validation-mapping-contract — 판정 기준 §Forbidden + 테스트 계약 ---- + + @ArchTest + static final ArchRule CONTROLLERS_DO_NOT_RETURN_DOMAIN_OR_ENTITY_TYPES = + methods() + .that() + .areDeclaredInClassesThat(CleanArchitectureRuleSupport.ARE_CONTROLLERS) + .and() + .arePublic() + .should() + .notHaveRawReturnType( + JavaClass.Predicates.resideInAnyPackage( + "..domain..", + "..adapter.outbound.persistence.entity..", + "..adapter.outbound.persistence.repository..")) + .as( + "controller methods must return DTO / record / Envelope / ResponseEntity — " + + "never a domain entity, JPA entity, or repository type " + + "(feature-boundary-validation-mapping-contract §Forbidden + §테스트 계약)") + .allowEmptyShould(true); + + @ArchTest + static final ArchRule OUTBOUND_ADAPTER_METHOD_RETURNS_ONLY_DOMAIN_OR_PRIMITIVES = + methods() + .that() + .areDeclaredInClassesThat( + JavaClass.Predicates.resideInAPackage("..adapter.outbound..") + .and(implementsApplicationLayerPort())) + // @Configuration factory classes are excluded: a @Bean method legitimately + // returns the adapter's own outbound port type for DI wiring (e.g. + // MessagingConfig#messagePublisher -> MessagePublisher) — that is + // composition-root assembly, not an adapter *response* method leaking an + // external type. B7 targets the latter (ACL leakage), not DI factories + // (feature-integration-adapter-templates Layer 1 @ConditionalOnProperty configs). + .and() + .areDeclaredInClassesThat() + .areNotAnnotatedWith("org.springframework.context.annotation.Configuration") + // @ConfigurationProperties settings holders are excluded for the same reason as + // @Configuration factories: they are config-binding types, not adapter *response* + // surfaces. Their accessors return bound config values (primitives, Duration, + // DataSize) and nested config records (e.g. OutboundHttpSettings#retry -> + // OutboundHttpSettings.Retry) — internal settings, not external HTTP / messaging + // responses, so B7's ACL-leakage target does not apply. + .and() + .areDeclaredInClassesThat() + .areNotAnnotatedWith( + "org.springframework.boot.context.properties.ConfigurationProperties") + // The canonical HTTP control plane and explicit legacy factories have a small exact + // method whitelist below. Names, packages, and arbitrary @Bean annotations do not bypass + // B7. + .and(notAnExactHttpControlPlaneMethod()) + // Redis provider binding and secret material provider SPIs likewise expose only a small + // exact set of typed composition accessors. Command/router/native runtime and secret + // value accessors are deliberately not exempt. + .and(notAnExactRedisCompositionMethod()) + .and() + // Package-private implementation types cannot expose their methods outside the + // adapter package. B7 protects the externally reachable adapter API, not internal + // records and fault-injection seams used to implement that API. + .areDeclaredInClassesThat() + .arePublic() + .and() + .arePublic() + .and() + .areNotStatic() + .should() + .notHaveRawReturnType( + JavaClass.Predicates.resideInAnyPackage( + "..adapter.outbound..", + "..adapter.inbound.web..", + "..adapter.outbound.persistence..")) + .as( + "B7: outbound application-port adapter public methods must return domain types" + + " (or primitives/wrappers/Optional) — raw external response types must not" + + " escape the adapter package (feature-boundary-validation-mapping-contract B7" + + " ACL). @Configuration @Bean factory methods and @ConfigurationProperties" + + " settings holders are excluded — they assemble port bindings / bind config," + + " not adapter response surfaces.") + .allowEmptyShould(true); + + private static final Set B7_EXACT_HTTP_CONTROL_PLANE_METHODS = + // The HTTP Client Platform (feature-httpclient-platform) exposes no outbound adapter + // class that implements an application-layer port, so B7 has no HTTP control-plane + // method to exempt. The whitelist mechanism stays in place so a future application-port + // implementation must justify each exact accessor rather than widen the rule. + Set.of(); + + private static DescribedPredicate notAnExactHttpControlPlaneMethod() { + return new DescribedPredicate<>("not an exact HTTP control-plane accessor/factory") { + @Override + public boolean test(JavaMethod method) { + return !B7_EXACT_HTTP_CONTROL_PLANE_METHODS.contains(ExactMethodSignature.from(method)); + } + }; + } + + private static final Set B7_EXACT_REDIS_COMPOSITION_METHODS = + Set.of( + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings", + "dataAuthentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings", + "dataTls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone", + "dataAuthentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Standalone", + "dataTls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", + "sentinelAuthentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", + "sentinelTls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", + "dataAuthentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Sentinel", + "dataTls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster", + "dataAuthentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Authentication"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Cluster", + "dataTls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisDeploymentSettings$Tls"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", + "topology", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$Topology"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", + "standalone", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$StandaloneProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", + "sentinel", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", + "cluster", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$ClusterProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", + "authentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$DeploymentProperties", + "tls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties", + "authentication", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$AuthenticationProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$SentinelProperties", + "tls", + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$TlsProperties"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.config.RedisProviderSettings$RuntimeProperties", + "clientSettings", + "dev.caskeleton.adapter.outbound.cache.redis.runtime.RedisClientRuntimeSettings"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.security.RedisCredentialMaterialProvider", + "resolve", + List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"), + "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisCredentialMaterial"), + signature( + "dev.caskeleton.adapter.outbound.cache.redis.security.RedisTrustMaterialProvider", + "resolve", + List.of("dev.caskeleton.adapter.outbound.cache.redis.security.RedisSecretReference"), + "dev.caskeleton.adapter.outbound.cache.redis.security.VersionedRedisTrustMaterial")); + + private static DescribedPredicate notAnExactRedisCompositionMethod() { + return new DescribedPredicate<>("not an exact Redis composition accessor or material SPI") { + @Override + public boolean test(JavaMethod method) { + return !B7_EXACT_REDIS_COMPOSITION_METHODS.contains(ExactMethodSignature.from(method)); + } + }; + } + + private static ExactMethodSignature signature(String owner, String name, String returnType) { + return signature(owner, name, List.of(), returnType); + } + + private static ExactMethodSignature signature( + String owner, String name, List parameters, String returnType) { + return new ExactMethodSignature(owner, name, parameters, returnType); + } + + private record ExactMethodSignature( + String owner, String name, List parameters, String returnType) { + + private ExactMethodSignature { + parameters = List.copyOf(parameters); + } + + private static ExactMethodSignature from(JavaMethod method) { + List parameterTypes = + method.getRawParameterTypes().stream().map(JavaClass::getFullName).toList(); + return new ExactMethodSignature( + method.getOwner().getFullName(), + method.getName(), + parameterTypes, + method.getRawReturnType().getFullName()); + } + } + + private static DescribedPredicate implementsApplicationLayerPort() { + return DescribedPredicate.describe( + "implement an application-layer port", + type -> + type.getAllRawInterfaces().stream() + .anyMatch( + iface -> + iface.getPackageName().contains(".application.") + || iface.getPackageName().endsWith(".application"))); + } + + @ArchTest + static final ArchRule VALID_CASCADE_DEPTH_AT_MOST_THREE = + classes() + .that() + .resideInAPackage("..adapter.inbound.web..dto..") + .should(notHaveValidCascadeDeeperThan(3)) + .as( + "B4: @Valid cascade depth must be ≤ 3 to bound nested-DTO recursion cost " + + "(feature-boundary-validation-mapping-contract B4 — DoS defense)") + .allowEmptyShould(true); + + // ---- feature-business-rule-validation-contract C1/D1 (validation stays at the web boundary) + // ---- + + @ArchTest + static final ArchRule VALIDATION_CONSTRAINTS_STAY_AT_WEB_BOUNDARY = + noClasses() + .that() + .resideInAnyPackage("..domain..", "..application..") + .should() + .dependOnClassesThat() + .resideInAPackage("jakarta.validation..") + .as( + "C1/D1: Bean Validation (jakarta.validation @NotNull / @Valid / ...) is the " + + "syntax layer and must stay at the adapter-web request boundary. Domain " + + "invariants live in domain model / value-object constructors and application " + + "policy is plain Java — neither is expressed with annotation-driven " + + "validation (feature-business-rule-validation-contract C1/D1, boundary B4 " + + "layering). Production code is scanned only — fixtures in " + + "..architecture.violations.. are out of scope under " + + "ImportOption.DoNotIncludeTests.") + .allowEmptyShould(true); + + private static ArchCondition notHaveValidCascadeDeeperThan(int maxDepth) { + return new ArchCondition<>("not have @Valid cascade deeper than " + maxDepth + " nested DTOs") { + @Override + public void check(JavaClass item, ConditionEvents events) { + int depth = computeValidDepth(item, new HashSet<>(), 0); + if (depth > maxDepth) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " has @Valid cascade depth " + + depth + + " (max allowed: " + + maxDepth + + ")")); + } + } + + private int computeValidDepth(JavaClass type, Set visited, int currentDepth) { + if (!visited.add(type.getName())) { + return currentDepth; + } + int maxFound = currentDepth; + for (JavaField field : type.getFields()) { + boolean hasValid = false; + for (JavaAnnotation a : field.getAnnotations()) { + if (a.getRawType().getName().equals("jakarta.validation.Valid")) { + hasValid = true; + break; + } + } + if (hasValid) { + JavaClass fieldType = field.getRawType(); + if (fieldType.getPackageName().startsWith("dev.caskeleton")) { + int sub = computeValidDepth(fieldType, visited, currentDepth + 1); + if (sub > maxFound) { + maxFound = sub; + } + } + } + } + return maxFound; + } + }; + } + + private static ArchCondition notBeAnnotatedWithJsonIgnoreUnknown() { + return new ArchCondition<>( + "not be annotated with @JsonIgnoreProperties(ignoreUnknown = true)") { + @Override + public void check(JavaClass item, ConditionEvents events) { + item.tryGetAnnotationOfType("com.fasterxml.jackson.annotation.JsonIgnoreProperties") + .ifPresent( + annotation -> { + Object ignoreUnknown = annotation.get("ignoreUnknown").orElse(null); + if (Boolean.TRUE.equals(ignoreUnknown)) { + events.add( + SimpleConditionEvent.violated( + item, + "Class " + + item.getName() + + " is annotated with " + + "@JsonIgnoreProperties(ignoreUnknown = true) — " + + "use field-level @JsonIgnore on the specific " + + "field instead (B1)")); + } + }); + } + }; + } + + // ---- feature-application-query-bypass-contract D1 (read/query port purity guardrail) ---- + + @ArchTest + static final ArchRule QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES = + methods() + .that() + .areDeclaredInClassesThat( + JavaClass.Predicates.resideInAPackage("..application..") + .and(JavaClass.Predicates.simpleNameEndingWith("QueryPort"))) + .and() + .arePublic() + .should( + notLeakDomainJpaOrWebThroughReturnType( + "..domain..", + "..adapter..", + "jakarta.persistence..", + "javax.persistence..", + "org.springframework.web..", + "org.hibernate..")) + .as( + "D1 QUERY_PORTS_DO_NOT_LEAK_DOMAIN_JPA_OR_WEB_TYPES: application read/query ports" + + " (classes whose simple name ends with 'QueryPort') must return" + + " application-layer projection DTOs — never a domain aggregate, JPA entity, or" + + " web type, INCLUDING through generic type arguments like List" + + " (checked via JavaType.getAllInvolvedRawTypes(), since a raw-return-type check" + + " alone misses generic leakage) (feature-application-query-bypass-contract D1" + + " core purity guardrail). The through-aggregate read path (repository ports" + + " returning the domain aggregate) is a separate, equally-valid choice and is" + + " intentionally out of this rule's scope.") + .allowEmptyShould(true); + + private static ArchCondition notLeakDomainJpaOrWebThroughReturnType( + String... forbiddenPackages) { + DescribedPredicate forbidden = + JavaClass.Predicates.resideInAnyPackage(forbiddenPackages); + return new ArchCondition<>( + "return application projection DTOs only " + + "(no domain / adapter / JPA / web types, including generic type arguments)") { + @Override + public void check(JavaMethod method, ConditionEvents events) { + // getAllInvolvedRawTypes() flattens the return type plus every generic type + // argument (recursively) to their raw classes — so List surfaces + // both java.util.List AND DomainType, which a raw-return-type predicate misses. + for (JavaClass involved : method.getReturnType().getAllInvolvedRawTypes()) { + if (forbidden.test(involved)) { + events.add( + SimpleConditionEvent.violated( + method, + "Method " + + method.getFullName() + + " leaks " + + involved.getName() + + " through its return type (directly or as a generic type argument)" + + " — a *QueryPort must return application-layer projection DTOs " + + "(feature-application-query-bypass-contract D1)")); + } + } + } + }; + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java index 72b6132e..ecc3b33d 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/violations/package-info.java @@ -3,8 +3,8 @@ * dev.caskeleton.bootstrap.architecture.ArchitectureViolationFixtureTest}. * *

Each class in this package is an intentional violation of exactly one ArchUnit rule declared - * in {@link dev.caskeleton.bootstrap.architecture.CleanArchitectureTest}. The fixtures live under - * {@code src/test/java/...}, so the main {@code @AnalyzeClasses(importOptions = + * in the architecture suites in {@code dev.caskeleton.bootstrap.architecture}. The fixtures live + * under {@code src/test/java/...}, so the main {@code @AnalyzeClasses(importOptions = * DoNotIncludeTests.class)} excludes them from the production architecture suite — they never * trigger a "real" violation. * diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderCompositionTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderCompositionTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderCompositionTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderCompositionTest.java index 01b3ae95..3832799a 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderCompositionTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderCompositionTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderScanCollisionTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderScanCollisionTest.java index 9b68344c..de75e828 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencyProviderScanCollisionTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencyProviderScanCollisionTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettingsTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettingsTest.java similarity index 95% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettingsTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettingsTest.java index d35a110d..ab9e5160 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/idempotency/IdempotencySettingsTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/idempotency/IdempotencySettingsTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.idempotency; +package dev.caskeleton.bootstrap.autoconfigure.idempotency; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/CapabilityEntityScanRegistrationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/CapabilityEntityScanRegistrationTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/CapabilityEntityScanRegistrationTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/CapabilityEntityScanRegistrationTest.java index c3350c2e..15c3dbe2 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/persistencejpa/CapabilityEntityScanRegistrationTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/jpa/CapabilityEntityScanRegistrationTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.autoconfigure.persistencejpa; +package dev.caskeleton.bootstrap.autoconfigure.jpa; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPortTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPortTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPortTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPortTest.java index dd5afa49..5c42324d 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/lock/MeteredDistributedLockPortTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/lock/MeteredDistributedLockPortTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.lock; +package dev.caskeleton.bootstrap.autoconfigure.lock; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributorTest.java similarity index 95% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributorTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributorTest.java index 9684a947..ac3974ad 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/mongo/MongoPlatformHealthContributorTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/mongo/MongoPlatformHealthContributorTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.mongo; +package dev.caskeleton.bootstrap.autoconfigure.mongo; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationAutoConfigurationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationAutoConfigurationTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationAutoConfigurationTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationAutoConfigurationTest.java index 1a979f33..f7d207d3 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationAutoConfigurationTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationAutoConfigurationTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationLegacyNamespaceRetirementTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationLegacyNamespaceRetirementTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationLegacyNamespaceRetirementTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationLegacyNamespaceRetirementTest.java index 73f00361..7dcd6f32 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationLegacyNamespaceRetirementTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationLegacyNamespaceRetirementTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationModeSsotTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationModeSsotTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationModeSsotTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationModeSsotTest.java index 00250205..8bcb1ca2 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationModeSsotTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationModeSsotTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPayloadAtRestContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPayloadAtRestContractTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPayloadAtRestContractTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPayloadAtRestContractTest.java index bb363099..84aa5d55 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPayloadAtRestContractTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPayloadAtRestContractTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPlatformReachabilityTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformReachabilityTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPlatformReachabilityTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformReachabilityTest.java index 4b2ecbcf..085df98a 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationPlatformReachabilityTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationPlatformReachabilityTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationSecretsValidationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationSecretsValidationTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationSecretsValidationTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationSecretsValidationTest.java index ee85f7bd..b2e5ddfd 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationSecretsValidationTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationSecretsValidationTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; @@ -7,7 +7,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformMode; import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformSettings; import dev.caskeleton.application.notification.platform.security.SecretPurpose; -import dev.caskeleton.bootstrap.notification.NotificationPlatformSecretsConfig.NotificationSecretsSettings; +import dev.caskeleton.bootstrap.autoconfigure.notification.NotificationPlatformSecretsConfig.NotificationSecretsSettings; import java.util.Base64; import java.util.LinkedHashMap; import java.util.Map; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationWorkerLifecycleTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationWorkerLifecycleTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationWorkerLifecycleTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationWorkerLifecycleTest.java index 49c7c0a3..371e1432 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/NotificationWorkerLifecycleTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/NotificationWorkerLifecycleTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification; +package dev.caskeleton.bootstrap.autoconfigure.notification; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationTest.java index 8f17cacf..bc45382e 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/notification/observation/NotificationObservationTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/notification/observation/NotificationObservationTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.notification.observation; +package dev.caskeleton.bootstrap.autoconfigure.notification.observation; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderAttemptLimiter; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderRuntime; import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderRuntimeRegistry; +import dev.caskeleton.adapter.outbound.notification.platform.observation.MicrometerNotificationMetrics; import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationHealthReporter; import dev.caskeleton.adapter.outbound.notification.platform.observation.NotificationServingThresholds; import dev.caskeleton.application.notification.platform.api.ProviderId; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfigTest.java similarity index 87% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfigTest.java index c88efe12..c6637f8d 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxConfigTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxConfigTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidatorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidatorTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidatorTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidatorTest.java index 96abe8c7..1043be33 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxRelayBrokerRequirementValidatorTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxRelayBrokerRequirementValidatorTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxSettingsTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettingsTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxSettingsTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettingsTest.java index 7fbde97d..5ea26086 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/outbox/OutboxSettingsTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/outbox/OutboxSettingsTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.outbox; +package dev.caskeleton.bootstrap.autoconfigure.outbox; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidatorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidatorTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidatorTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidatorTest.java index 987b8100..f3699174 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisActivationValidatorTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisActivationValidatorTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.runtime; +package dev.caskeleton.bootstrap.autoconfigure.redis; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityCompositionTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityCompositionTest.java index e76b2832..d1e8c906 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/redis/RedisCapabilityCompositionTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisCapabilityCompositionTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.redis; +package dev.caskeleton.bootstrap.autoconfigure.redis; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessorTest.java similarity index 95% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessorTest.java index 935f6fb7..352441f7 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/redis/RedisReadinessGroupPostProcessorTest.java @@ -1,7 +1,9 @@ -package dev.caskeleton.bootstrap.runtime; +package dev.caskeleton.bootstrap.autoconfigure.redis; import static org.assertj.core.api.Assertions.assertThat; +import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration; +import dev.caskeleton.bootstrap.runtime.ReadinessGroup; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; @@ -11,7 +13,6 @@ import org.springframework.boot.WebApplicationType; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.env.YamlPropertySourceLoader; import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroup; import org.springframework.boot.health.actuate.endpoint.HealthEndpointGroups; import org.springframework.boot.health.autoconfigure.actuate.endpoint.HealthEndpointAutoConfiguration; @@ -25,7 +26,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.env.MutablePropertySources; import org.springframework.core.env.PropertySourcesPropertyResolver; -import org.springframework.core.io.ClassPathResource; /** * Starts a real Actuator health context in each of the three deployment shapes. @@ -51,11 +51,9 @@ class RedisReadinessGroupPostProcessorTest { */ private static String shippedReadinessInclude() throws IOException { MutablePropertySources sources = new MutablePropertySources(); - new YamlPropertySourceLoader() - .load("application", new ClassPathResource("application.yml")) - .forEach(sources::addLast); + ShippedConfiguration.propertySources().forEach(sources::addLast); return new PropertySourcesPropertyResolver(sources) - .getProperty(RedisReadinessGroupPostProcessor.READINESS_INCLUDE); + .getProperty(ReadinessGroup.INCLUDE_PROPERTY); } private static ConfigurableApplicationContext boot(Map properties) @@ -71,7 +69,7 @@ class RedisReadinessGroupPostProcessorTest { // The setting that turned a naming mistake into a startup failure. On, exactly as in // production. defaults.put("management.endpoint.health.validate-group-membership", "true"); - defaults.put(RedisReadinessGroupPostProcessor.READINESS_INCLUDE, shippedReadinessInclude()); + defaults.put(ReadinessGroup.INCLUDE_PROPERTY, shippedReadinessInclude()); defaults.putAll(properties); return new SpringApplicationBuilder(HealthContext.class) .web(WebApplicationType.NONE) diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorderTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorderTest.java similarity index 96% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorderTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorderTest.java index 6a5007c4..1a6b4a01 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/MicrometerSpanErrorRecorderTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/MicrometerSpanErrorRecorderTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingActivationContextTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingActivationContextTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingActivationContextTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingActivationContextTest.java index 002ffa31..60307e98 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingActivationContextTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingActivationContextTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolverTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolverTest.java similarity index 98% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolverTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolverTest.java index 9de7ce05..208f2689 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSampleRateResolverTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSampleRateResolverTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessorTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessorTest.java similarity index 96% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessorTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessorTest.java index 584e5966..53980fb4 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSamplingEnvironmentPostProcessorTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSamplingEnvironmentPostProcessorTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import static org.assertj.core.api.Assertions.assertThat; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSettingsTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettingsTest.java similarity index 99% rename from src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSettingsTest.java rename to src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettingsTest.java index 2309e2af..77b89de5 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/tracing/TracingSettingsTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/autoconfigure/tracing/TracingSettingsTest.java @@ -1,4 +1,4 @@ -package dev.caskeleton.bootstrap.tracing; +package dev.caskeleton.bootstrap.autoconfigure.tracing; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java new file mode 100644 index 00000000..ecd63b0d --- /dev/null +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/ConfigurationNamespaceContractTest.java @@ -0,0 +1,192 @@ +package dev.caskeleton.bootstrap.contract; + +import static org.assertj.core.api.Assertions.assertThat; + +import dev.caskeleton.bootstrap.contract.support.ShippedConfiguration; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * The configuration namespaces this application owns, pinned (BOOT-001). + * + *

The shipped configuration grew three application-owned roots — {@code ca-skeleton}, {@code + * app} and {@code backend} — and nothing recorded why, so "where do I set this?" had three answers + * and a reader had to know which. Two capabilities were registered under both of the first two. + * + *

Why this pins the split instead of removing it

+ * + *

The obvious fix — rename everything to one root — is wrong for at least one of the trees, and + * the reason is worth stating because it is not visible from the YAML. + * + *

{@code ca-skeleton.*} keys are populated from {@code APP_*} environment variables through + * explicit {@code ${...}} placeholders, so the property name and the variable name are independent: + * renaming the property changes nothing an operator types. {@code app.httpclient.*} is different. + * Its list-valued settings are bound straight from the environment by relaxed binding, which is why + * {@code HttpClientEnvironmentKeys} exists at all and why it derives its templates from {@code + * APP_HTTPCLIENT_}. Renaming that prefix to {@code ca-skeleton.httpclient} would rename every + * {@code APP_HTTPCLIENT_*} variable to {@code CA_SKELETON_HTTPCLIENT_*} — breaking the repository's + * documented convention that application-owned environment uses the {@code APP_} prefix, and every + * deployment that sets one. + * + *

{@code app.messaging.*} is out of reach for a second reason: it belongs to the vendored {@code + * messaging:*} platform, which by design depends on no type of this application and owns its own + * configuration contract. + * + *

So the roots stay, and what changes is that they can no longer grow quietly. A fourth root, or + * a new prefix under an existing one, fails here and has to be argued for. + */ +class ConfigurationNamespaceContractTest { + + /** + * The roots a shipped resource may declare. + * + *

{@code spring} and {@code management} / {@code server} / {@code logging} / {@code springdoc} + * are framework namespaces — this application does not own their shape, only their values. + */ + private static final Set ALLOWED_ROOTS = + Set.of( + "spring", + "management", + "server", + "logging", + "springdoc", + "ca-skeleton", + "app", + "backend"); + + /** + * Every {@code @ConfigurationProperties} prefix and {@code PREFIX} constant this leaf declares. + * + *

Listed exhaustively rather than matched by pattern. A pattern would accept a new prefix on + * the day it is written, which is the moment the decision is actually being made. + */ + private static final Set DECLARED_PREFIXES = + new TreeSet<>( + List.of( + "app.fileserver-platform", + "app.httpclient", + "app.jpa-platform", + "ca-skeleton.async.executor", + "ca-skeleton.bootstrap", + "ca-skeleton.capabilities", + "ca-skeleton.capabilities.idempotency", + "ca-skeleton.domain-context", + "ca-skeleton.idempotency", + "ca-skeleton.logging", + "ca-skeleton.notification.platform.secrets", + "ca-skeleton.outbox", + "ca-skeleton.privacy", + "ca-skeleton.runtime", + "ca-skeleton.secret-source", + "ca-skeleton.security", + "ca-skeleton.tracing")); + + private static final Pattern PREFIX_ANNOTATION = + Pattern.compile("@ConfigurationProperties\\(\\s*(?:prefix\\s*=\\s*)?\"([^\"]+)\""); + + private static final Pattern PREFIX_CONSTANT = + Pattern.compile("String PREFIX\\s*=\\s*\"([^\"]+)\""); + + private static final Pattern ROOT_KEY = Pattern.compile("(?m)^([a-zA-Z][A-Za-z0-9_-]*):\\s*$"); + + @Test + @DisplayName("the shipped configuration declares no root beyond the recorded ones") + void shippedConfigurationDeclaresOnlyRecordedRoots() { + Set roots = new TreeSet<>(); + Matcher root = ROOT_KEY.matcher(ShippedConfiguration.text()); + while (root.find()) { + roots.add(root.group(1)); + } + + assertThat(roots) + .as( + "a fourth application-owned root would give 'where do I set this?' another answer. " + + "Add it to ALLOWED_ROOTS only with the reason written down — see this class's " + + "Javadoc for why the existing three are not simply merged (BOOT-001).") + .isSubsetOf(ALLOWED_ROOTS); + assertThat(roots) + .as("the scan must actually reach the shipped roots, or this check passes vacuously") + .contains("ca-skeleton", "app", "spring"); + } + + @Test + @DisplayName("every declared settings prefix is one this contract records") + void everyDeclaredPrefixIsRecorded() throws IOException { + Set found = new LinkedHashSet<>(); + Path main = repositoryRoot().resolve("src/app-bootstrap/src/main/java"); + try (Stream sources = Files.walk(main)) { + for (Path source : sources.filter(p -> p.toString().endsWith(".java")).toList()) { + String text = Files.readString(source); + for (Pattern pattern : List.of(PREFIX_ANNOTATION, PREFIX_CONSTANT)) { + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + found.add(matcher.group(1)); + } + } + } + } + + assertThat(found) + .as("the scan must find the leaf's settings prefixes, or this check passes vacuously") + .hasSizeGreaterThan(10); + assertThat(found) + .as( + "a settings prefix that is not recorded here is a namespace decision nobody reviewed. " + + "Record it, and prefer ca-skeleton.* unless the keys bind directly from the " + + "environment — see this class's Javadoc (BOOT-001).") + .isSubsetOf(DECLARED_PREFIXES); + } + + @Test + @DisplayName("each capability's configuration lives in exactly one shipped file") + void eachCapabilityHasOneHome() { + // The operator-facing half of BOOT-001, and the half the split actually fixed: notification was + // registered under both roots, in one 974-line file, 470 lines apart. The two trees are still + // two + // trees — one is the delivery platform, the other the R0 legacy seam — but they are now in + // config/notification.yml together, so looking up "notification" reaches both. + assertThat(ShippedConfiguration.resources()) + .as("the split must be in place for this contract to mean anything") + .contains("config/notification.yml"); + + String notification = fileText("config/notification.yml"); + assertThat(notification) + .as("the delivery platform tree must be in the notification file") + .contains("ca-skeleton:") + .contains("platform:"); + assertThat(notification) + .as("the legacy provider-selection tree must be in the same file, not a root away") + .contains("app:"); + } + + private static String fileText(String resource) { + String marker = "# --- " + resource + " ---"; + String all = ShippedConfiguration.text(); + int start = all.indexOf(marker); + assertThat(start).as("%s must be among the shipped resources", resource).isNotNegative(); + int next = all.indexOf("# --- ", start + marker.length()); + return next < 0 ? all.substring(start) : all.substring(start, next); + } + + private static Path repositoryRoot() { + Path candidate = Path.of("").toAbsolutePath(); + while (candidate != null + && !Files.isRegularFile(candidate.resolve("src/config/architecture/modules.json"))) { + candidate = candidate.getParent(); + } + if (candidate == null) { + throw new IllegalStateException("could not locate the repository root from the test cwd"); + } + return candidate; + } +} diff --git a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java index 133e061f..b38b8495 100644 --- a/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java +++ b/src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/contract/DistributedTracingContractTest.java @@ -5,8 +5,8 @@ import static org.assertj.core.api.Assertions.assertThat; import dev.caskeleton.adapter.inbound.web.observability.MdcKeys; import dev.caskeleton.adapter.inbound.web.observability.ResponseMetaFactory; import dev.caskeleton.bootstrap.async.AsyncContextTaskDecorator; +import dev.caskeleton.bootstrap.autoconfigure.tracing.TracingSamplingRateGauge; import dev.caskeleton.bootstrap.contract.support.RepositoryContractResources; -import dev.caskeleton.bootstrap.tracing.TracingSamplingRateGauge; import dev.caskeleton.shared.concurrency.DomainContextPropagatorFactory; import dev.caskeleton.shared.concurrency.DomainContextStrategy; import dev.caskeleton.shared.response.ResponseMeta; @@ -37,10 +37,10 @@ import org.yaml.snakeyaml.Yaml; * *