refactor: 빌드 최적화 및 ci 수정

This commit is contained in:
donghyeon-ka
2026-09-17 15:24:31 +09:00
parent 944a1e348b
commit ace8aaaef6
263 changed files with 1975 additions and 3130 deletions
@@ -1,11 +1,13 @@
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
}
// Redis SDK leaf — see docs/superpowers/specs/2026-08-07-redis-wrapper-typed-api-design.md.
//
// The design models the SDK as separate Gradle modules. This repository's fail-closed module
// registry outranks that layout, so the module boundaries are packages under
// dev.caskeleton.adapter.outbound.cache.redis.sdk and RedisSdkModuleBoundaryTest enforces them.
apply plugin: 'ca.spring-library'
apply plugin: 'ca.spring-config'
dependencies {
// Registered edges the semantic port adapters need. The SDK's *main* source imports nothing from
// them today — the semantic cache/session/idempotency/rate-limit adapters that did were removed
@@ -193,4 +195,3 @@ strictTestLanes {
}
}
}
+5 -3
View File
@@ -1,10 +1,12 @@
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
}
// Driven adapter for provider-neutral file publication and legacy CSV export. The only qualified
// R2 provider is local-persistent; shared-mounted/NFS and SFTP are not stand-ins or implemented
// capabilities. Its IO path uses only the JDK. Spring Boot autoconfigure supplies explicit,
// disabled-default R1/R2 composition and SLF4J remains the diagnostics API.
apply plugin: 'ca.spring-library'
apply plugin: 'ca.spring-config'
description = 'Outbound adapter: file publication (R1 CSV export, R2 local-persistent) plus the ' + \
'local filesystem content platform behind the HTTP Fileserver'
+5 -4
View File
@@ -1,8 +1,9 @@
plugins {
id 'ca.spring-library'
id 'java-test-fixtures'
}
// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
apply plugin: 'ca.spring-library'
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).
@@ -40,7 +40,7 @@ class MutualTlsHandshakeContractTest {
TlsFixture fixture = TlsFixture.trusted();
try (MockHttpServer server = MockHttpServer.startTls(fixture.serverSocketFactory(), true)) {
server.enqueueJson(200, "{\"id\":1,\"name\":\"mtls\"}");
ClientProfile profile = tlsProfile(server.uri("/"));
ClientProfile profile = tlsProfile(server.ipv4Uri("/"));
ApacheBlockingTransportProvider provider =
new ApacheBlockingTransportProvider(
Optional.empty(),
@@ -56,7 +56,7 @@ class MutualTlsHandshakeContractTest {
NoopLifecycleListener.INSTANCE))
.build()
.get()
.uri(server.uri("/users/1"))
.uri(server.ipv4Uri("/users/1"))
.retrieve()
.body(String.class);
assertThat(body).contains("mtls");
@@ -72,7 +72,7 @@ class MutualTlsHandshakeContractTest {
TlsFixture fixture = TlsFixture.trusted();
try (MockHttpServer server = MockHttpServer.startTls(fixture.serverSocketFactory(), true)) {
server.enqueueJson(200, "{\"id\":1,\"name\":\"never\"}");
ClientProfile profile = tlsProfile(server.uri("/"));
ClientProfile profile = tlsProfile(server.ipv4Uri("/"));
ApacheBlockingTransportProvider provider =
new ApacheBlockingTransportProvider(
Optional.empty(),
@@ -91,7 +91,7 @@ class MutualTlsHandshakeContractTest {
// so the failure can surface either during the handshake or on the first read. Both are
// failures; what must never happen is the request being served.
assertThatThrownBy(
() -> client.get().uri(server.uri("/users/1")).retrieve().body(String.class))
() -> client.get().uri(server.ipv4Uri("/users/1")).retrieve().body(String.class))
.isInstanceOf(RuntimeException.class);
} finally {
provider.close(
@@ -119,7 +119,7 @@ class MutualTlsHandshakeContractTest {
try (MockHttpServer server =
MockHttpServer.startTls(serverFixture.serverSocketFactory(), false)) {
server.enqueueJson(200, "{\"id\":1,\"name\":\"never\"}");
ClientProfile profile = tlsProfile(server.uri("/"));
ClientProfile profile = tlsProfile(server.ipv4Uri("/"));
Throwable captured = captureFailure(profile, server, unrelatedClientTrust);
assertPermanent(captured);
}
@@ -128,7 +128,7 @@ class MutualTlsHandshakeContractTest {
private void assertPermanentTlsFailure(TlsFixture fixture) throws Exception {
try (MockHttpServer server = MockHttpServer.startTls(fixture.serverSocketFactory(), false)) {
server.enqueueJson(200, "{\"id\":1,\"name\":\"never\"}");
ClientProfile profile = tlsProfile(server.uri("/"));
ClientProfile profile = tlsProfile(server.ipv4Uri("/"));
Throwable captured = captureFailure(profile, server, fixture);
assertPermanent(captured);
}
@@ -150,7 +150,7 @@ class MutualTlsHandshakeContractTest {
NoopLifecycleListener.INSTANCE))
.build();
try {
client.get().uri(server.uri("/users/1")).retrieve().body(String.class);
client.get().uri(server.ipv4Uri("/users/1")).retrieve().body(String.class);
throw new AssertionError("the handshake was expected to fail");
} catch (RuntimeException failure) {
return failure;
@@ -71,6 +71,11 @@ public final class MockHttpServer implements AutoCloseable {
return server.url(path).uri();
}
/** Deterministic IPv4 loopback URI for TLS fixtures on dual-stack hosts. */
public URI ipv4Uri(String path) {
return server.url(path).newBuilder().host("127.0.0.1").build().uri();
}
public int port() {
return server.getPort();
}
@@ -49,6 +49,12 @@ public final class TlsFixture {
.signedBy(authority)
.commonName(serverCommonName)
.addSubjectAlternativeName(serverCommonName);
if ("localhost".equals(serverCommonName)) {
// MockWebServer binds an IPv4 loopback socket in this fixture environment while localhost may
// resolve to both 127.0.0.1 and ::1. Include the explicit IPv4 SAN so TLS tests can use a
// deterministic address without changing the certificate semantics under test.
server.addSubjectAlternativeName("127.0.0.1");
}
if (validity.isNegative()) {
long now = System.currentTimeMillis();
server.validityInterval(
+4 -2
View File
@@ -1,6 +1,8 @@
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
}
apply plugin: 'ca.spring-library'
apply plugin: 'ca.spring-config'
dependencies {
implementation project(':application-core')
@@ -1,5 +1,7 @@
apply plugin: 'ca.spring-library'
apply plugin: 'ca.spring-config'
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
}
dependencies {
implementation project(':application-core')
@@ -1,3 +1,8 @@
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
}
// Driven adapter: provider-neutral semantic object-storage ports plus a bounded local-development
// provider. Canonical app.object-storage activation is disabled by default. The old whole-byte[]
// filesystem/S3 adapters remain isolated, explicit legacy compatibility only.
@@ -10,9 +15,6 @@
// This sentence used to end "and this repo has no version catalog". That is false, and this file
// disproves it twice below with `libs.archunit.junit5` and `libs.jqwik`. Module scope is a locking
// decision; the catalog just has no awssdk entry.
apply plugin: 'ca.spring-library'
apply plugin: 'ca.spring-config'
description = 'Outbound adapter: object storage (S3/MinIO + local filesystem)'
@@ -33,7 +35,7 @@ strictTestLanes {
dependencyManagement {
imports {
mavenBom "software.amazon.awssdk:bom:${awsSdkVersion}"
mavenBom "software.amazon.awssdk:bom:${libs.versions.awsSdk.get()}"
}
}
@@ -1,9 +1,11 @@
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
id 'java-test-fixtures'
id 'ca.jpa-evidence'
}
// Shared test code as a Gradle test-fixtures variant — ADR-BUILD-001.
apply plugin: 'ca.spring-library'
apply plugin: 'ca.spring-config'
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 /
@@ -29,8 +31,6 @@ strictTestLanes {
sourceSet('jpaPlatformPerformanceTest') { compilesAgainst 'main', 'testFixtures' }
}
ext.jpaPostgreSqlEvidenceImage = 'postgres:16-alpine'
dependencies {
implementation project(':application-core')
implementation project(':shared-contract')
@@ -108,6 +108,8 @@ dependencies {
// through `requires(...)` turns on `failOnNoMatchingTests` AND the post-run check that the named
// selector actually executed, so a renamed readiness class fails its lane instead of leaving it
// with nothing to run.
String jpaPostgreSqlEvidenceImage = providers.gradleProperty('jpaPostgreSqlEvidenceImage')
.getOrElse('postgres:16-alpine')
String readinessPackage = 'dev.caskeleton.adapter.outbound.persistence.readiness'
Map<String, String> postgresqlReadinessLanes = [
postgresqlLifecycleIntegrationTest : 'PostgreSqlLifecycleIntegrationTest',
@@ -155,7 +157,7 @@ strictTestLanes.lanes.named('postgresqlSecurityBaselineIntegrationTest').configu
}
// The task itself stays, and its name is not negotiable: config/jpa/readiness-cards.yaml lists it as
// a support task of the `jpa-security-baseline` card, and gradle/jpa-evidence.gradle resolves every
// a support task of the `jpa-security-baseline` card, and the ca.jpa-evidence qualification plugin resolves every
// listed path through `tasks.findByName` and fails the build when one is missing. What changed is
// what it checks. Grepping the fixture's source text for 'runtimeRoleCannotCreateInApplicationSchema',
// 'assertDockerAvailable' and '42501' passed on three strings in a comment and proved nothing about
@@ -355,11 +357,11 @@ apiSurface {
]
}
apply from: rootProject.file('gradle/jpa-evidence.gradle')
// The JPA readiness registry describes this platform's lanes and resolves their task paths, so it
// runs with this leaf's `check` rather than with all 62. The task itself is registered by
// gradle/qualification/jpa-qualification.gradle, which the root applies.
// the ca.jpa-qualification plugin from build-qualification, which the root applies.
tasks.named('check') {
dependsOn rootProject.tasks.named('verifyJpaReadinessRegistry')
}
@@ -30,7 +30,7 @@ public final class JpaReleaseRendering {
private static final Pattern WORKFLOW_PROMOTION_LOOP = Pattern.compile("for major in ([0-9 ]+);");
private static final Pattern QUOTED_MAJOR = Pattern.compile("\"(\\d+)\"");
private static final Pattern COMPATIBILITY_TARGET =
Pattern.compile("target=PostgreSQL\\s+(\\d+)");
Pattern.compile("^\\s*target:\\s*PostgreSQL\\s+(\\d+)\\s*$", Pattern.MULTILINE);
private JpaReleaseRendering() {}
@@ -128,9 +128,9 @@ public final class JpaReleaseRendering {
* the conclusion this repository's own review pass reached before checking the workflow
* directory.
*
* <p>The target is read from the status artifact the lane writes rather than from its filename. A
* filename is a label; the artifact is what a promotion decision would actually be read from, so
* a lane that stops recording its target stops counting as a lane.
* <p>The target is read from the workflow matrix declaration rather than from its filename. The
* status artifact receives that same matrix value through {@code TARGET}; a lane that stops
* declaring its PostgreSQL target therefore stops counting as a lane.
*/
public static List<Integer> compatibilityLaneTargets(String workflow) {
Objects.requireNonNull(workflow, "workflow");
@@ -36,10 +36,9 @@ design package's assumed module layout onto this leaf lives in
`verifyCleanArchitectureDependencies` only checks that resolved edges are a subset of the declared
ones, so an unused permission passes every run; `MongoRegistryPermissionParityTest` checks the
other direction and fails when the two sets differ.
- `runtime_memberships` is `["app-bootstrap"]`, and the composition root really does declare
`implementation(project(':adapter:outbound:persistence-mongo'))` — with the reactive starter and
the reactivestreams driver excluded, because there is no reactive port in the shipped Stable
scope. `RuntimeMembershipClasspathAgreementTest` compares the registry against the resolved
- The composition root declares `implementation(project(':adapter:outbound:persistence-mongo'))`
with the reactive starter and the reactivestreams driver excluded, because there is no reactive
port in the shipped Stable scope. The actual Gradle runtime graph is the runtime SSOT;
runtime classpath, so the membership cannot drift from what the jar carries. Property-only
activation therefore works here: the switch turns on a module that already ships, and shipping it
off is not the same contract as leaving it out, because absence cannot be reversed at deploy time
@@ -13,11 +13,11 @@ document/repository/mapper와 application 또는 domain port 구현을 추가할
### shipped runtime에 들어 있고, property가 그 스위치다
`app-bootstrap`은 이 leaf에 project dependency를 두고(`src/app-bootstrap/build.gradle`,
reactive starter와 reactivestreams driver는 exclude), registry의 `runtime_memberships`
`["app-bootstrap"]`이다. 두 사실은 `RuntimeMembershipClasspathAgreementTest`가 runtime classpath와
비교해 붙잡는다. 그래서 `ca-skeleton.persistence-mongo.enabled=true`실제로 동작하는 master
switch다 — 없는 모듈을 부르는 property가 아니라, 이미 jar에 들어 있는 모듈을 켜는 스위치다.
`app-bootstrap`은 이 leaf에 project dependency를 둔다(`src/app-bootstrap/build.gradle`; reactive
starter와 reactivestreams driver는 exclude). runtime 포함 여부의 SSOT는 이 실제 Gradle graph이고
`MongoRegistryPermissionParityTest`는 composition edge가 존재하는지도 확인한다. 그래서
`ca-skeleton.persistence-mongo.enabled=true`없는 모듈을 부르는 property가 아니라 이미 application
classpath에 들어 있는 모듈을 켜는 master switch다.
빠져 있는 모듈은 꺼진 모듈과 같은 계약이 아니다. 부재는 배포 시점에 되돌릴 수 없고, gating 결함을
전부 가린다. 존재하지 않는 코드는 자기 condition이 무엇이라고 말하든 bean을 하나도 들고 있지 않기
@@ -1,10 +1,11 @@
plugins {
id 'ca.spring-library'
id 'ca.spring-config'
id 'java-test-fixtures'
}
// 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: 'ca.spring-library'
apply plugin: 'ca.spring-config'
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
+4 -2
View File
@@ -1,7 +1,9 @@
plugins {
id 'ca.spring-library'
}
// Shared base for outbound integration adapters: correlation, fail-open dependency
// logging, and the @Configuration seam. Depended on by messaging/cache/notification/httpclient.
apply plugin: 'ca.spring-library'
dependencies {
implementation 'org.springframework.boot:spring-boot-autoconfigure'
implementation 'org.slf4j:slf4j-api'