Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/167-httpclient-module-inventory.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

401 lines
24 KiB
Plaintext

adapter-outbound-httpclient — 모듈 인벤토리
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T05:19:48+00:00
=== tracked file 총계 ===
$ git ls-files 'adapter/outbound/httpclient' | wc -l
370
exit=0
$ for d in $(git ls-files 'adapter/outbound/httpclient/src' | sed 's|^adapter/outbound/httpclient/src/||' | cut -d/ -f1 | sort -u); do n=$(git ls-files "adapter/outbound/httpclient/src/$d" | wc -l); l=$(git ls-files "adapter/outbound/httpclient/src/$d" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}'); echo "$d: $n files, $l LOC"; done
httpClientPerformanceTest: 7 files, 495 LOC
jmh: 2 files, 130 LOC
main: 260 files, 15004 LOC
test: 62 files, 6049 LOC
testkit: 35 files, 2754 LOC
exit=0
=== main 패키지 배치 ===
$ git ls-files 'adapter/outbound/httpclient/src/main' | grep '\.java$' | sed 's|.*/dev/caskeleton/||' | xargs -n1 dirname | sort | uniq -c | sort -rn
39 adapter/outbound/httpclient/resilience
29 adapter/outbound/httpclient/profile
23 adapter/outbound/httpclient/api/error
21 adapter/outbound/httpclient/security
19 adapter/outbound/httpclient/restclient
18 adapter/outbound/httpclient/auth
17 adapter/outbound/httpclient/webclient
14 adapter/outbound/httpclient/service
11 adapter/outbound/httpclient/dynamic
10 adapter/outbound/httpclient/transport
9 adapter/outbound/httpclient/api/result
7 adapter/outbound/httpclient/api/body
6 adapter/outbound/httpclient/reactor
6 adapter/outbound/httpclient/observation
6 adapter/outbound/httpclient/api/operation
5 adapter/outbound/httpclient/api
5 adapter/outbound/httpclient/apache
4 adapter/outbound/httpclient/migration
4 adapter/outbound/httpclient/jdk
4 adapter/outbound/httpclient/http3
3 adapter/outbound/httpclient/spring7
exit=0
=== test 패키지 배치 ===
$ git ls-files 'adapter/outbound/httpclient/src/test' | grep '\.java$' | sed 's|.*/dev/caskeleton/||' | xargs -n1 dirname | sort | uniq -c | sort -rn
8 adapter/outbound/httpclient/resilience
7 adapter/outbound/httpclient/contract
5 adapter/outbound/httpclient/webclient
5 adapter/outbound/httpclient/service
5 adapter/outbound/httpclient/security
5 adapter/outbound/httpclient/restclient
3 adapter/outbound/httpclient/architecture
2 adapter/outbound/httpclient/testkit
2 adapter/outbound/httpclient/profile
2 adapter/outbound/httpclient/observation
2 adapter/outbound/httpclient/migration
2 adapter/outbound/httpclient/jdk
2 adapter/outbound/httpclient/dynamic
2 adapter/outbound/httpclient/auth
2 adapter/outbound/httpclient/api/body
2 adapter/outbound/httpclient/apache
1 adapter/outbound/httpclient/transport
1 adapter/outbound/httpclient/spring7
1 adapter/outbound/httpclient/http3
1 adapter/outbound/httpclient/api/operation
1 adapter/outbound/httpclient/api/error
1 adapter/outbound/httpclient/api
exit=0
=== main 상위 30 ===
$ git ls-files 'adapter/outbound/httpclient/src/main' | grep '\.java$' | xargs wc -l | sort -rn | head -31
15004 total
330 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveHttpGateway.java
304 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/DefaultGenericHttpGateway.java
291 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/ClientProfileValidator.java
272 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveKernelHttpExchangeAdapter.java
269 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingAttemptExecutor.java
228 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DefaultDynamicTargetGateway.java
215 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/IpAddressClassifier.java
211 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/service/KernelHttpExchangeAdapter.java
206 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientResponseReader.java
190 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/http3/JettyHttp3TransportProvider.java
187 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/profile/ClientRuntimeRegistry.java
184 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/ReactiveAttemptExecutor.java
183 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/DefaultReactiveSseGateway.java
176 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TrustedTargetPolicy.java
176 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/security/TlsMaterialProvider.java
169 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingStreamingGateway.java
165 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/api/error/HttpFailureMetadata.java
154 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/service/ServiceOperationDescriptorScanner.java
153 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/apache/ApacheClientFactory.java
152 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/api/operation/HttpOperation.java
148 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/SingleFlightTokenLoader.java
144 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/RestClientRuntimeFactory.java
140 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/apache/ApacheFailureClassifier.java
137 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ReactorHttpClientFactory.java
137 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ReactorFailureClassifier.java
135 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/resilience/AttemptResiliencePipeline.java
133 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/resilience/DefaultRetryEligibilityEngine.java
132 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/restclient/BlockingClientRuntime.java
128 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/auth/OAuth2CredentialProvider.java
123 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/webclient/WebClientRuntimeFactory.java
exit=0
=== 비 Java 파일 ===
$ git ls-files 'adapter/outbound/httpclient' | grep -v '\.java$'
adapter/outbound/httpclient/CLAUDE.md
adapter/outbound/httpclient/README.md
adapter/outbound/httpclient/build.gradle
adapter/outbound/httpclient/gradle.lockfile
exit=0
=== 레지스트리 엔트리 ===
$ python3 -c "
import json;d=json.load(open('config/architecture/modules.json'))
for m in d['modules']:
if m['id']=='adapter-outbound-httpclient': print(json.dumps(m,ensure_ascii=False,indent=2))
"
{
"id": "adapter-outbound-httpclient",
"gradle_path": ":adapter:outbound:httpclient",
"source_path": "src/adapter/outbound/httpclient",
"allowed_dependencies": [
"domain-core",
"application-core",
"shared-contract",
"adapter-outbound-support"
],
"runtime_memberships": [
"app-bootstrap"
]
}
exit=0
=== build.gradle ===
$ cat -n 'adapter/outbound/httpclient/build.gradle'
1 // Outbound HTTP Client Platform leaf — see
2 // docs/superpowers/specs/2026-08-08-httpclient-platform-design.md (design package) and
3 // docs/httpclient/repository-adaptation.md (how the design's 19 library modules map here).
4 //
5 // The design models the platform as 19 separate Gradle modules. This repository's fail-closed
6 // module registry (src/config/architecture/modules.json) outranks that layout, so the module
7 // boundaries are packages under dev.caskeleton.adapter.outbound.httpclient and
8 // HttpClientModuleBoundaryTest enforces the design's module dependency table.
9 description = 'Outbound adapter: HTTP client platform (typed clients, profiles, evidence-based retry)'
10
11 dependencies {
12 implementation project(':application-core')
13 implementation project(':shared-contract')
14 implementation project(':adapter:outbound:support')
15
16 // Spring client layer. `httpclient-core-api` must not reach these; ArchUnit enforces it.
17 implementation 'org.springframework.boot:spring-boot-autoconfigure'
18 implementation 'org.springframework:spring-web'
19 implementation 'org.springframework:spring-webflux'
20 implementation 'io.projectreactor:reactor-core'
21
22 // Transport providers. Apache HC5 is the blocking default, JDK HttpClient is the lightweight
23 // alternative (JDK built-in), Reactor Netty is the reactive default, Jetty carries the
24 // Experimental HTTP/3 transport that the Stable starter never auto-configures.
25 implementation 'org.apache.httpcomponents.client5:httpclient5'
26 implementation 'io.projectreactor.netty:reactor-netty-http'
27 implementation 'org.eclipse.jetty:jetty-client'
28 // HTTP/3 is Experimental and off by default, so its transport is compileOnly plus a test
29 // dependency rather than a runtime one. It used to be `implementation`, which put the whole
30 // QUIC/HTTP-3/QPACK stack on every deployment's runtime classpath — megabytes and an attack
31 // surface — to serve a feature the Stable starter never auto-configures. A deployment that
32 // opts into HTTP/3 adds `org.eclipse.jetty.http3:jetty-http3-client-transport` itself, and
33 // Http3CapabilityReport already refuses the transport when those classes are absent, so the
34 // failure mode is a startup error rather than a NoClassDefFoundError mid-call.
35 compileOnly 'org.eclipse.jetty.http3:jetty-http3-client-transport'
36 testImplementation 'org.eclipse.jetty.http3:jetty-http3-client-transport'
37
38 // Resilience4j supplies the execution primitives only. HTTP retry *eligibility* is owned by
39 // this module (design D-09) and never delegated to a generic retry library.
40 implementation 'io.github.resilience4j:resilience4j-retry:2.2.0'
41 implementation 'io.github.resilience4j:resilience4j-circuitbreaker:2.2.0'
42 implementation 'io.github.resilience4j:resilience4j-ratelimiter:2.2.0'
43 implementation 'io.github.resilience4j:resilience4j-bulkhead:2.2.0'
44 implementation 'io.github.resilience4j:resilience4j-micrometer:2.2.0'
45
46 implementation 'org.springframework.security:spring-security-oauth2-client'
47 implementation 'com.fasterxml.jackson.core:jackson-databind'
48 // Jackson 3, and not optional. `RestClientResponseReader.defaultConverters()` constructs a
49 // `JacksonJsonHttpMessageConverter`, which is Spring 7's converter and is built on
50 // `tools.jackson.databind.json.JsonMapper`. javac never noticed: the constructor's signature
51 // does not mention the type, so compiling the call needs nothing. Loading the class does, and
52 // Jackson 3 reached only this leaf's test and jmh classpaths — so the reader worked in every
53 // test and would have thrown NoClassDefFoundError in any deployment that did not happen to
54 // have Jackson 3 from somewhere else. The Eclipse compiler the editor runs reported it as an
55 // error, which is what it is. Jackson 2 stays: two files still read `JsonNode` with it.
56 implementation 'tools.jackson.core:jackson-databind'
57 implementation 'io.micrometer:micrometer-core'
58 implementation 'org.slf4j:slf4j-api'
59
60
61 // Testkit dependencies (design §28.1 test topology). They are test-scoped so no production
62 // module can depend on the testkit; the testkit source set inherits them by extending
63 // testImplementation, and the test lanes use them directly.
64 testImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
65 testImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
66 testImplementation 'org.testcontainers:testcontainers'
67 testImplementation 'org.testcontainers:testcontainers-junit-jupiter'
68 testImplementation 'org.testcontainers:testcontainers-toxiproxy'
69 testImplementation 'io.projectreactor:reactor-test'
70 testImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
71 testImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE'
72 }
73
74 // The testkit is its own source set, not part of `test`, because three lanes consume it and only
75 // one of them is a test lane. Reaching into `sourceSets.test.output` from `jmh` compiled under
76 // Gradle and could not be modelled by the IDE at all: a source set is test source there only when
77 // a Test task runs its output, `jmh` is driven by JavaExec, and IDE main source may not read IDE
78 // test source — so every testkit reference in the benchmarks was an unresolved type in the editor
79 // while the build was green. A source set nobody runs tests from is main source for all three
80 // consumers, which is what it always was.
81 //
82 // Performance certification and JMH benchmarks are separate source sets for their own reason: they
83 // are slow, they assert on resource bounds rather than behaviour, and they must never be part of
84 // the default unit lane.
85 strictTestLanes {
86 // The testkit compiles against exactly what a test does: `implementation` inheritance runs
87 // through testImplementation, so this is the module's own dependencies plus the test libraries.
88 sourceSet('testkit') { compilesAgainst 'main' }
89 sourceSet('httpClientPerformanceTest') { compilesAgainst 'main', 'testkit' }
90 sourceSet('jmh') { compilesAgainst 'main', 'testkit' }
91 }
92
93 // Every test lane compiles and runs against the testkit.
94 sourceSets.test {
95 compileClasspath += sourceSets.testkit.output
96 runtimeClasspath += sourceSets.testkit.output
97 }
98
99 dependencies {
100 // Testkit dependencies (design §28.1 test topology). They stay off the production
101 // configurations, so no production module can depend on the testkit.
102 testkitImplementation 'com.squareup.okhttp3:mockwebserver:4.12.0'
103 testkitImplementation 'com.squareup.okhttp3:okhttp-tls:4.12.0'
104 testkitImplementation 'org.testcontainers:testcontainers'
105 testkitImplementation 'org.testcontainers:testcontainers-junit-jupiter'
106 testkitImplementation 'org.testcontainers:testcontainers-toxiproxy'
107 testkitImplementation 'io.projectreactor:reactor-test'
108 testkitImplementation 'com.tngtech.archunit:archunit-junit5:1.3.0'
109 testkitImplementation 'io.projectreactor.tools:blockhound:1.0.17.RELEASE'
110
111 jmhImplementation 'org.openjdk.jmh:jmh-core:1.37'
112 jmhAnnotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess:1.37'
113 }
114
115 // UTF-8 is pinned for every JavaCompile task in the root build; this leaf no longer repeats it.
116
117 // JMH generates its harness classes at compile time. They are not our source, so the
118 // compile-time checker and -Werror are switched off for that source set only; applying them
119 // would fail the build on generated code we cannot edit.
120 tasks.named('compileJmhJava', JavaCompile) {
121 options.errorprone.enabled = false
122 options.compilerArgs.removeIf { it == '-Werror' }
123 }
124
125 // The bytecode analyser is disabled for the same generated harness, for the same reason.
126 tasks.named('spotbugsJmh') {
127 enabled = false
128 }
129
130 // Takes a Test task. The parameter is left untyped because the IDE's Gradle parser has no Gradle
131 // API on its classpath and reports the annotation as an unresolved type; Groovy dispatches the
132 // calls below dynamically either way.
133 Closure<Void> applyContractSelection = { task ->
134 // Cross-transport contract lane. The same semantic contract runs against every Stable transport;
135 // the transport under test is selected explicitly so a missing transport is an error, not a skip.
136 task.systemProperty 'httpclient.contract.transports',
137 (project.findProperty('httpclient.contract.transports') ?: 'apache,jdk,reactor').toString()
138 // Netty's strictest leak detector is on for every lane. It is only meaningful if it is actually
139 // live, so NettyLeakDetectionExtension asserts the level rather than trusting the flag reached
140 // the forked JVM.
141 task.systemProperty 'io.netty.leakDetection.level', 'paranoid'
142 // HTTP/3 is Experimental: it is never part of the default lane and never silently skipped.
143 task.systemProperty 'httpclient.http3.tests.enabled',
144 (project.findProperty('http3.tests.enabled') ?: 'false').toString()
145 }
146
147 tasks.named('test', Test) {
148 applyContractSelection(it)
149 // Two lanes are excluded from the default run for opposite reasons: the fault lane needs Docker
150 // and fails closed without it, and the BlockHound lane rewrites core JDK bytecode, which must
151 // not be imposed on every unit run.
152 useJUnitPlatform {
153 excludeTags 'quarantine', 'httpclient-fault', 'httpclient-blockhound'
154 }
155 }
156
157 // Four tag-selected lanes, declared rather than assembled.
158 //
159 // Two of them — the stable contract suite and the security suite — did not carry
160 // failOnNoDiscoveredTests at all. Five lanes were written by copying the block above, and the
161 // property that makes a lane mean anything was lost on two of the copies, so the cross-transport
162 // contract suite and the SSRF/credential-leak suite would each have reported success on discovering
163 // nothing. Declaring the lanes removes the opportunity: the convention has no opt-out.
164 strictTestLanes {
165 lane('httpClientBlockHoundTest') {
166 tag = 'httpclient-blockhound'
167 description = 'Proves no platform code blocks a Reactor event loop (design §18.2, §28.6).'
168 customize = { test ->
169 applyContractSelection(test)
170 // BlockHound instruments already-loaded JDK classes; Java 13+ needs this to redefine them.
171 test.jvmArgs '-XX:+AllowRedefinitionToAddDeleteMethods'
172 }
173 }
174 lane('httpClientStableContractTest') {
175 tag = 'httpclient-contract'
176 description = 'Runs the cross-transport stable contract suite (design §28.2, §33).'
177 customize = { test -> applyContractSelection(test) }
178 }
179 lane('httpClientSecurityTest') {
180 tag = 'httpclient-security'
181 description = 'Runs the SSRF, credential-leak, and cardinality suite (design §28.5, §28.7).'
182 customize = { test -> applyContractSelection(test) }
183 }
184 // Its own source set rather than a tag, so the source set is the selection.
185 lane('httpClientPerformanceTest') {
186 sourceSet = 'httpClientPerformanceTest'
187 description = 'Certifies pool, streaming, retry, and rotation resource bounds (design §28.8).'
188 customize = { test ->
189 applyContractSelection(test)
190 test.systemProperty 'performance.assertions.enabled',
191 (project.findProperty('performance.assertions.enabled') ?: 'false').toString()
192 }
193 }
194 lane('httpClientFailureInjectionTest') {
195 tag = 'httpclient-fault'
196 description = 'Runs the Toxiproxy fault-injection suite; fails closed without Docker ' +
197 '(design §28.3).'
198 customize = { test ->
199 applyContractSelection(test)
200 // The upstream image is mutable by default. Passing a digest here is what makes a red
201 // fault run attributable to this repository rather than to someone else's image push.
202 test.systemProperty 'httpclient.fault.httpbin.image',
203 (project.findProperty('httpclient.fault.httpbin.image')
204 ?: 'kennethreitz/httpbin:latest').toString()
205 }
206 }
207 }
208
209
210 tasks.register('jmh', JavaExec) {
211 group = 'verification'
212 description = 'Runs the JMH benchmarks for the blocking and reactive clients (design §28.8).'
213 mainClass = 'org.openjdk.jmh.Main'
214 classpath = sourceSets.jmh.runtimeClasspath
215 args '-rf', 'json', '-rff', layout.buildDirectory.file('reports/jmh/result.json').get().asFile.absolutePath
216 }
217
218 // Spring 6.2 / 7.0 compatibility lanes. This repository's Spring Boot 4.0 baseline pins Spring
219 // Framework 7, so the 6.2 lane verifies the *API surface* the common packages compile against
220 // rather than executing on a 6.2 distribution; the limitation is recorded in
221 // docs/httpclient/support-matrix.md instead of being hidden behind a green check.
222 tasks.register('spring62ApiSurfaceScan', Test) {
223 group = 'verification'
224 description = 'Scans the common packages for Spring 6.2 API-surface confinement. NOT a 6.2 runtime.'
225 testClassesDirs = sourceSets.test.output.classesDirs
226 classpath = sourceSets.test.runtimeClasspath
227 useJUnitPlatform { includeTags 'httpclient-spring62-surface' }
228 failOnNoDiscoveredTests = true
229 outputs.upToDateWhen { false }
230 }
231
232 // Which lanes gate an ordinary build, and which do not.
233 //
234 // The specialised lanes existed but hung off nothing: `check` ran only `test`, so the SSRF suite,
235 // the BlockHound lane, the cross-transport contract and the Spring 6.2 surface scan were green in
236 // CI only because a workflow happened to name them, and green locally because nobody ran them.
237 // The four below are hermetic and fast — no Docker, no network, no machine-dependent thresholds —
238 // so they belong in `check`.
239 //
240 // httpClientFailureInjectionTest (needs Docker), httpClientPerformanceTest (machine-dependent
241 // bounds) and jmh (minutes) stay out deliberately. Attaching them would make `check` fail on a
242 // laptop without Docker, which teaches people to skip `check`.
243 tasks.named('check') {
244 dependsOn 'httpClientStableContractTest',
245 'httpClientSecurityTest',
246 'httpClientBlockHoundTest',
247 'spring62ApiSurfaceScan'
248 }
249
250 tasks.register('spring70CompatibilityTest', Test) {
251 group = 'verification'
252 description = 'Runs the contract suite on the repository Spring 7 baseline (design §29).'
253 testClassesDirs = sourceSets.test.output.classesDirs
254 classpath = sourceSets.test.runtimeClasspath
255 useJUnitPlatform { includeTags 'httpclient-contract' }
256 applyContractSelection(it)
257 outputs.upToDateWhen { false }
258 }
exit=0
=== leaf 밖 소비자 ===
$ git grep -l 'dev.caskeleton.adapter.outbound.httpclient' -- . | grep -v '^adapter/outbound/httpclient/' | sed 's|/src/.*||' | sort | uniq -c
17 app-bootstrap
exit=0