// Application entry point. Wires the default runtime module set and runs Spring Boot. // Optional leaves require an explicit registry allowance plus a composition-root dependency. apply plugin: 'org.springframework.boot' apply from: "${rootProject.projectDir}/gradle/strict-qualification-test.gradle" // The sample fixture (sampleFixture -> sample-portfolio -> objectstorage) pulls software.amazon.awssdk:s3 // onto the sample-on test classpath; its version is managed by the AWS SDK v2 BOM (NOT the Spring Boot // BOM). Import that BOM at this module's scope so the transitive s3 dependency resolves for the ArchUnit // sample-on analysis. AWS-SDK version SSOT = ext.awsSdkVersion. dependencyManagement { imports { mavenBom "software.amazon.awssdk:bom:${awsSdkVersion}" } } // feature-sample-removal-adoption-contract D7 — ordinary tests may inspect the sample fixture, // while sampleOffTest compiles the same core suite without the sample project on either classpath. configurations { sampleFixture { canBeConsumed = false canBeResolved = false } } sourceSets { sampleOffTest { java.srcDirs = sourceSets.test.java.srcDirs java.srcDir 'src/sampleOffTest/java' resources.srcDirs = sourceSets.test.resources.srcDirs compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } functionalTest { java.srcDir 'src/functionalTest/java' resources.srcDir 'src/functionalTest/resources' } conditionalTransportTest { java.srcDir 'src/conditionalTransportTest/java' resources.srcDir 'src/conditionalTransportTest/resources' compileClasspath += sourceSets.main.output runtimeClasspath += sourceSets.main.output } } configurations { testCompileClasspath.extendsFrom sampleFixture testRuntimeClasspath.extendsFrom sampleFixture sampleOffTestImplementation.extendsFrom testImplementation sampleOffTestCompileOnly.extendsFrom testCompileOnly sampleOffTestRuntimeOnly.extendsFrom testRuntimeOnly sampleOffTestAnnotationProcessor.extendsFrom testAnnotationProcessor } dependencies { implementation project(':domain-core') implementation project(':application-core') implementation project(':adapter:outbound:persistence-jpa') implementation project(':adapter:outbound:support') implementation project(':adapter:outbound:messaging') implementation project(':adapter:outbound:cache-redis') implementation project(':adapter:outbound:notification') implementation project(':adapter:outbound:fileserver') implementation project(':adapter:outbound:httpclient') implementation project(':adapter:outbound:identifier') implementation project(':adapter:inbound:web') implementation project(':shared-contract') 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' // Boot 4 Flyway API/autoconfiguration: the composition root drives startup migration // (MigrationStartupConfig). See README. implementation 'org.springframework.boot:spring-boot-flyway' // Flyway API: MigrationStartupRunner directly invokes Flyway. Kept explicit for readability. implementation 'org.flywaydb:flyway-core' // Micrometer core for OutboxMetrics meters (no-op without a MeterRegistry). See README. implementation 'io.micrometer:micrometer-core' // OTel/Micrometer tracer runtime (exporter stays off until an OTLP endpoint is set). See README. implementation 'io.micrometer:micrometer-tracing-bridge-otel' implementation 'io.opentelemetry:opentelemetry-exporter-otlp' // Actuator + Prometheus registry (health/info/prometheus/loggers endpoints). implementation 'org.springframework.boot:spring-boot-starter-actuator' // The composition root wires the OAuth2 credential provider for the HTTP Client Platform, // so it must see OAuth2AuthorizedClientManager. The adapter keeps the dependency internal. implementation 'org.springframework.security:spring-security-oauth2-client' implementation 'io.micrometer:micrometer-registry-prometheus' // Security types for ManagementSecurityConfig (not reachable via adapter-web's implementation dep). See README. implementation 'org.springframework.boot:spring-boot-starter-security' // test-only: ArchUnit needs actuator types to verify the health-shape guardrail. See README. testImplementation 'org.springframework.boot:spring-boot-starter-actuator' // 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' // 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. testRuntimeOnly 'org.postgresql:postgresql' // test-only: JPA/Hikari for the outbox tests' minimal context (not leaked from rdbms). See README. 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' testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0' // 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. testImplementation 'org.yaml:snakeyaml' // sample-on only: ArchUnit analyses the reference impl. sampleOffTest intentionally omits it. sampleFixture project(':sample-portfolio') // test-only: ArchUnit violation fixtures intentionally import forbidden types. See README. testCompileOnly 'org.springframework:spring-tx' // test-only: streaming/websocket violation fixtures import these forbidden packages. See README. testCompileOnly 'org.springframework:spring-webmvc' // SseEmitter, ResponseBodyEmitter, StreamingResponseBody testCompileOnly 'org.springframework:spring-websocket' // org.springframework.web.socket.. testCompileOnly 'jakarta.websocket:jakarta.websocket-api' // jakarta.websocket.. // test-only: transport-free domain-event fixtures import these forbidden broker/wire packages. See README. testCompileOnly '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.. // JSON log encoder; implementation (not runtimeOnly) because StartupFailures uses StructuredArguments at compile time. See README. implementation 'net.logstash.logback:logstash-logback-encoder:8.0' // 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' // 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' // functional-test-only: executes isolated Gradle fixtures without placing Gradle's SLF4J // provider on the ordinary test runtime classpath. functionalTestImplementation gradleTestKit() functionalTestImplementation 'org.junit.jupiter:junit-jupiter' functionalTestImplementation 'org.assertj:assertj-core' functionalTestRuntimeOnly 'org.junit.platform:junit-platform-launcher' // Explicit qualification-only composition. These projects remain absent from main // api/implementation/compileOnly/runtimeOnly and therefore from both shipped runtime graphs. conditionalTransportTestImplementation project(':adapter:inbound:graphql') conditionalTransportTestImplementation project(':adapter:inbound:grpc') conditionalTransportTestImplementation project(':adapter:inbound:websocket') conditionalTransportTestImplementation 'org.junit.jupiter:junit-jupiter' conditionalTransportTestImplementation 'org.assertj:assertj-core' conditionalTransportTestImplementation 'org.yaml:snakeyaml' conditionalTransportTestRuntimeOnly 'org.junit.platform:junit-platform-launcher' } def repositoryRootForContractTests = rootProject.projectDir.parentFile.absolutePath def contractRegistriesDirectory = rootProject.projectDir.parentFile.toPath() .resolve('docs/registries').toFile() tasks.withType(Test).configureEach { systemProperty 'ca.repository.root', repositoryRootForContractTests } // Pin UTC for the TEST JVM so timestamp tests are host-locale-independent (production UTC owned elsewhere). See README. tasks.named('test') { inputs.dir(contractRegistriesDirectory) .withPathSensitivity(PathSensitivity.RELATIVE) jvmArgs '-Duser.timezone=UTC' } tasks.register('sampleOffCompile') { group = 'verification' description = 'Compiles the complete app-bootstrap test corpus without sample-portfolio.' dependsOn tasks.named(sourceSets.sampleOffTest.classesTaskName) } def sampleOffQualification = registerStrictQualificationTest( name: 'sampleOffTest', sourceSet: sourceSets.sampleOffTest, requiredClasses: [ 'dev.caskeleton.bootstrap.contract.SampleOffClasspathContractTest' ], description: 'Runs the exact no-skip sample-off classpath qualification.') sampleOffQualification.configure { shouldRunAfter tasks.named('test') systemProperty 'ca.sample.mode', 'off' } tasks.register('functionalTest', Test) { group = 'verification' description = 'Runs isolated Gradle TestKit contracts for repository build behavior.' testClassesDirs = sourceSets.functionalTest.output.classesDirs classpath = sourceSets.functionalTest.runtimeClasspath useJUnitPlatform() failOnNoDiscoveredTests = true shouldRunAfter tasks.named('test') jvmArgs '-Duser.timezone=UTC' } def conditionalTransportCompositionQualification = registerStrictQualificationTest( name: 'conditionalTransportCompositionTest', sourceSet: sourceSets.conditionalTransportTest, requiredClasses: [ 'dev.caskeleton.bootstrap.transport.ConditionalTransportCompositionContractTest' ], description: 'Proves the explicit test-only GraphQL/gRPC/WebSocket opt-in classpath.') conditionalTransportCompositionQualification.configure { shouldRunAfter tasks.named('test') } tasks.named('check') { dependsOn tasks.named('functionalTest') } // feature-developer-experience-contract D2/D7 delegation: the DX entrypoint verifies production // isolation and the sample-on build contract. sample-off is the separate sampleOffTest task. tasks.register('bootstrapSampleContract', Test) { group = 'developer experience' description = 'Runs the delegated sample production-isolation/build contract for bootstrap.' testClassesDirs = sourceSets.test.output.classesDirs classpath = sourceSets.test.runtimeClasspath useJUnitPlatform() filter { includeTestsMatching 'dev.caskeleton.bootstrap.contract.SampleRemovalSmokeContractTest' } outputs.upToDateWhen { false } jvmArgs '-Duser.timezone=UTC' } bootJar { mainClass = 'dev.caskeleton.bootstrap.CaSkeletonApplication' } tasks.register('stageDockerJar', Sync) { dependsOn tasks.named('bootJar') from(tasks.named('bootJar').flatMap { it.archiveFile }) into(layout.buildDirectory.dir('docker')) rename { 'application.jar' } } // Run from the repo's src/ root and inject src/.env into the Java process environment. // Boot 4 initializes profiles/logging before spring-dotenv can reliably contribute .env values. bootRun { workingDir = rootProject.projectDir doFirst { File envFile = rootProject.file('.env') if (!envFile.isFile()) { return } envFile.eachLine { raw -> String line = raw.trim() if (line.isEmpty() || line.startsWith('#') || !line.contains('=')) { return } int separator = line.indexOf('=') String key = line.substring(0, separator).trim() String value = line.substring(separator + 1).trim() if (!key.isEmpty() && System.getenv(key) == null && !environment.containsKey(key)) { environment key, value } } } }