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>
165 lines
14 KiB
Plaintext
165 lines
14 KiB
Plaintext
Sub-scope 06 (httpclient) service + dynamic + observation + migration — manifest and probes
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T05:36:11+00:00
|
|
|
|
=== OWNED FILES ===
|
|
2af09e541e44e5fe5a7cf24b15136642768a4087 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/CallScopedDnsPin.java 78
|
|
27618ac790d4e0046ae8dc9ccfe822a9ad04adc8 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/CanonicalTarget.java 41
|
|
22074cb132f9d4d512170e12714c4ade7d4b0a20 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DefaultDynamicTargetGateway.java 228
|
|
b7fcfaa0a7cdc49c6b08dedc00e57d60f45caa69 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DynamicCredentialBinding.java 79
|
|
ac3fc51793c5fb8b94ae7f9622ef2efa28202343 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DynamicTargetGateway.java 21
|
|
f321f921eb9a532d0646a693bd708c27900948c5 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DynamicTargetPolicy.java 86
|
|
f2437231ac5e58c5c4330e42c27d60d785b9245d main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DynamicTargetPolicyName.java 16
|
|
71577ef97db1dcdd53278b6a290917a12332ff2e main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/IpAddressClassifier.java 215
|
|
960c7a4f75ed4bb91fa15aca5eba0620fff90ee4 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/PinnedTarget.java 24
|
|
50d4d3bf482d7c01534c9291acdecf395d552858 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/TargetCanonicalizer.java 112
|
|
29a3df649ea8a3a92008e518f495a792d75c51b8 main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/ValidatedDnsResolver.java 89
|
|
e43820f9778a041ec21f43f3131b22535b351592 main/java/dev/caskeleton/adapter/outbound/httpclient/migration/MigrationFinding.java 37
|
|
bb3563961e27ea83ec9d98066378ffe645d5f7a9 main/java/dev/caskeleton/adapter/outbound/httpclient/migration/RestTemplateInventory.java 37
|
|
6975590b25ec9c54b609b38417c3b9ebc4a5ff10 main/java/dev/caskeleton/adapter/outbound/httpclient/migration/RestTemplateInventoryScanner.java 59
|
|
c732b6ab9d542765795d0e5762f929010f809e8d main/java/dev/caskeleton/adapter/outbound/httpclient/migration/RestTemplateToRestClientAdapter.java 31
|
|
1dbb0bc7d98e76add4e87c26855ea8bd0e8bea8b main/java/dev/caskeleton/adapter/outbound/httpclient/observation/AttemptObservation.java 91
|
|
07b76d79376e25d75d9a234128ad41f393b7447d main/java/dev/caskeleton/adapter/outbound/httpclient/observation/HttpClientObservationNames.java 38
|
|
3b3fa7a105542ea7a4fa37b704d6c7b801d59337 main/java/dev/caskeleton/adapter/outbound/httpclient/observation/HttpClientTagPolicy.java 48
|
|
4577e52c32022a0e22db855e2c1f3ce996e6b236 main/java/dev/caskeleton/adapter/outbound/httpclient/observation/LogicalCallObservation.java 86
|
|
8666481f62760563c8879a9aa50925a6c9e149e8 main/java/dev/caskeleton/adapter/outbound/httpclient/observation/SafeHttpLogEvent.java 65
|
|
a5e69de9d6450b425396ac9563d2d6bb72dc531f main/java/dev/caskeleton/adapter/outbound/httpclient/observation/SensitiveValueRedactor.java 53
|
|
52bb01e25a00172aa0fcde9d27abb494ce115aba main/java/dev/caskeleton/adapter/outbound/httpclient/service/BlockingServiceInvocationHandler.java 52
|
|
a6a4b2c442a54d0db9c67f25b67a4d9969fff801 main/java/dev/caskeleton/adapter/outbound/httpclient/service/DefaultHttpServiceRegistry.java 115
|
|
c36bc593bf52dc041194e6ee20b2a8a9a939f189 main/java/dev/caskeleton/adapter/outbound/httpclient/service/DefaultReactiveHttpServiceRegistry.java 108
|
|
6a755427420edbcef4159b66a5ad65d81ff73176 main/java/dev/caskeleton/adapter/outbound/httpclient/service/HttpClientProfile.java 21
|
|
b2f36166a930c990b0389ed1be9923cce7e19dc5 main/java/dev/caskeleton/adapter/outbound/httpclient/service/HttpOperationPolicy.java 33
|
|
3b49db43679a3fd95703ce5e599c212820499009 main/java/dev/caskeleton/adapter/outbound/httpclient/service/HttpServiceRegistry.java 17
|
|
59929fedf0b1ab68e78c60271dea5a4127a12235 main/java/dev/caskeleton/adapter/outbound/httpclient/service/KernelHttpExchangeAdapter.java 211
|
|
7930325d46b5da36e0c87ea60fc917c5bc429044 main/java/dev/caskeleton/adapter/outbound/httpclient/service/OperationContextHolder.java 38
|
|
bcad49a3265db6dd3157220f85ce8ad7cdb29d55 main/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveHttpServiceRegistry.java 11
|
|
935bf7584884f90f86c315d861466dcaf88bcff6 main/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveKernelHttpExchangeAdapter.java 272
|
|
968873f1b16a0d6fe5dad9068651fce1b020bee4 main/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveOperationContext.java 21
|
|
20522e681dcae77d02b1a2ec30602ac2ea7c2113 main/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveServiceInvocationHandler.java 79
|
|
bce1f13e9c22a53e49fc019b930acf4a812a9a3a main/java/dev/caskeleton/adapter/outbound/httpclient/service/ServiceOperationDescriptor.java 32
|
|
b7ecaf988db71e1a4f90eb9f5ff3aa654eed3de0 main/java/dev/caskeleton/adapter/outbound/httpclient/service/ServiceOperationDescriptorScanner.java 154
|
|
d276cc605f4f92df779c48b847cbaa946788d3ff test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/HttpClientModuleBoundaryTest.java 115
|
|
76e10b273255a7e8faf974420802a897fdcf2271 test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PlatformClasses.java 40
|
|
2aa907b1b3f9df7a5beb1094b2a057db7d318efe test/java/dev/caskeleton/adapter/outbound/httpclient/architecture/PublicApiArchitectureTest.java 152
|
|
eb356ac8a3a3092902a7caa9cbc09d593167f8b5 test/java/dev/caskeleton/adapter/outbound/httpclient/contract/AllStableTransportsContractTest.java 217
|
|
bd864272cb9ba0fe3d330f5bd19b2ef76e6b296d test/java/dev/caskeleton/adapter/outbound/httpclient/contract/FailureInjectionContractTest.java 86
|
|
bf64d702c186503c074297bd9a2e13c27a2072b1 test/java/dev/caskeleton/adapter/outbound/httpclient/contract/NegotiatedProtocolContractTest.java 175
|
|
8bddf0864197c9b35d50f20fe55b7d761fb68bef test/java/dev/caskeleton/adapter/outbound/httpclient/contract/ResourceLifecycleContractTest.java 42
|
|
b14b51f5ffe6151719542b0be41d1abfc77a8858 test/java/dev/caskeleton/adapter/outbound/httpclient/contract/RetrySafetyContractTest.java 15
|
|
4aa7e00c81f2f679cfe7a38bb51ec8eba8c55316 test/java/dev/caskeleton/adapter/outbound/httpclient/contract/SecurityContractTest.java 84
|
|
12732b6ced283b139cff9db6d0bf3ad7a1e39ff8 test/java/dev/caskeleton/adapter/outbound/httpclient/contract/StatefulUpstreamRetryContractTest.java 170
|
|
e795be1532576002788b322c9e7020ffbdd1fe02 test/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DynamicTargetSecurityTest.java 253
|
|
efc598351139295ac9485b5fc5ce5fe473a94697 test/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/TargetCanonicalizerTest.java 76
|
|
ac0708d2aefb5465a388847f60583f5d98f8fc50 test/java/dev/caskeleton/adapter/outbound/httpclient/migration/RestTemplateBoundaryTest.java 45
|
|
002cfe85746b58db8f5b1835be984426b9702d89 test/java/dev/caskeleton/adapter/outbound/httpclient/migration/RestTemplateToRestClientAdapterTest.java 64
|
|
8663d7ee8c483d6a6bd77c2a3d018ac44e92bfe5 test/java/dev/caskeleton/adapter/outbound/httpclient/observation/DeclaredMetricsAreEmittedTest.java 131
|
|
470f72a9496f91468355c26b563d36a6e9ff1a19 test/java/dev/caskeleton/adapter/outbound/httpclient/observation/HttpClientTagPolicyTest.java 85
|
|
2b4770cd22b6faf5c0a0c1bdaaeb6855f5d9d390 test/java/dev/caskeleton/adapter/outbound/httpclient/service/BlockingHttpServiceRegistryTest.java 76
|
|
a9522043a7c32ca58b7c486aa1e1d651f1801c22 test/java/dev/caskeleton/adapter/outbound/httpclient/service/BlockingReactiveSignatureSeparationTest.java 43
|
|
7cd7f5e356477c0d95ec5091e83862aa6e673949 test/java/dev/caskeleton/adapter/outbound/httpclient/service/ReactiveHttpServiceRegistryTest.java 89
|
|
99cbf7319f1fbfbd56cd687ef18211de2911b3db test/java/dev/caskeleton/adapter/outbound/httpclient/service/ServiceSignatureValidationTest.java 67
|
|
ff643ff91f7840c09b3699789d506f995992aa01 test/java/dev/caskeleton/adapter/outbound/httpclient/service/TypedClientPlatformPolicyTest.java 136
|
|
|
|
main: 35 test: 21
|
|
=== 8.1 approved 맵의 수명 ===
|
|
$ grep -n 'approved' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/ValidatedDnsResolver.java
|
|
27: * approved five minutes ago is not evidence about the host now, which is the entire premise of
|
|
37: private final Map<String, List<InetAddress>> approved = new ConcurrentHashMap<>();
|
|
58: approved.remove(host);
|
|
65: approved.put(host, immutable);
|
|
74: public List<InetAddress> approvedAddresses(String host) {
|
|
75: return approved.getOrDefault(host, List.of());
|
|
79: approved.remove(host);
|
|
exit=0
|
|
|
|
$ grep -n 'resolver.forget' -B 3 adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/DefaultDynamicTargetGateway.java
|
|
118- } finally {
|
|
119- // Discarded at the end of the hop. Retaining it would grow without bound and, worse,
|
|
120- // would let a later call reuse an approval that was only ever made for this one.
|
|
121: resolver.forget(pinned.target().host());
|
|
exit=0
|
|
|
|
$ git grep -n 'approvedAddresses(' -- 'adapter/outbound/httpclient/src/main' 'app-bootstrap/src/main' | grep -v CallScopedDnsPin | grep -v PinnedTarget
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/dynamic/ValidatedDnsResolver.java:74: public List<InetAddress> approvedAddresses(String host) {
|
|
exit=0
|
|
|
|
=== 8.2 전송이 핀을 통해 해석하는가 ===
|
|
$ grep -n 'CallScopedDnsPin::addressesFor\|ValidatedAddressResolverGroup' -r app-bootstrap/src/main adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient
|
|
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/autoconfigure/httpclient/HttpClientTransportAutoConfiguration.java:81: return Optional.of(CallScopedDnsPin::addressesFor);
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ReactorHttpClientFactory.java:66: AddressResolverGroup<?> resolver = new ValidatedAddressResolverGroup(approvedAddresses.get());
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ValidatedAddressResolverGroup.java:24:public final class ValidatedAddressResolverGroup extends AddressResolverGroup<InetSocketAddress> {
|
|
adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/reactor/ValidatedAddressResolverGroup.java:28: public ValidatedAddressResolverGroup(Function<String, List<InetAddress>> approvedAddresses) {
|
|
exit=0
|
|
|
|
=== 8.3 관측 태그 폐쇄성 ===
|
|
$ grep -n 'Set.of\|reject\|ALLOWED' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/observation/HttpClientTagPolicy.java | head -20
|
|
9: * <p>The policy rejects an unknown tag name instead of accepting it: a metric backend cannot undo a
|
|
15: private static final Set<String> ALLOWED =
|
|
16: Set.of(
|
|
39: return ALLOWED;
|
|
43: if (!ALLOWED.contains(name)) {
|
|
exit=0
|
|
|
|
$ grep -n 'query\|drop\|redact' adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/observation/SensitiveValueRedactor.java | head -12
|
|
11: * <p>Query values are dropped entirely rather than masked per key: an allowlist of "safe" query
|
|
44: /** Returns the URI without userinfo, query, or fragment. */
|
|
exit=0
|
|
|
|
=== 8.4 아키텍처 test와 계약 lane ===
|
|
$ git ls-files 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture' 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/contract' | xargs -n1 basename
|
|
HttpClientModuleBoundaryTest.java
|
|
PlatformClasses.java
|
|
PublicApiArchitectureTest.java
|
|
AllStableTransportsContractTest.java
|
|
FailureInjectionContractTest.java
|
|
NegotiatedProtocolContractTest.java
|
|
ResourceLifecycleContractTest.java
|
|
RetrySafetyContractTest.java
|
|
SecurityContractTest.java
|
|
StatefulUpstreamRetryContractTest.java
|
|
exit=0
|
|
|
|
$ git grep -h ' void [a-z]\|@Tag(' -- 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/architecture' 'adapter/outbound/httpclient/src/test/java/dev/caskeleton/adapter/outbound/httpclient/contract' | sed 's/^ *//' | sort -u | head -40
|
|
@Tag("httpclient-contract")
|
|
@Tag("httpclient-fault")
|
|
@Tag("httpclient-security")
|
|
@Tag("httpclient-spring62-surface")
|
|
void aResetPeerProducesStableEvidenceRatherThanAGenericFailure() throws Exception {
|
|
void absoluteUriIsRejectedOnEveryTransport(String transport) throws Exception {
|
|
void anApacheProfileCannotClaimHttp2() {
|
|
void connectionsSurviveErrorStatusDecodeFailureAndUnreadStreams() throws Exception {
|
|
void coreApiDependsOnNothingInsideThePlatform() {
|
|
void crossOriginRedirectDoesNotForwardCredentials() throws Exception {
|
|
void doesNotRetryANonIdempotentWriteAgainstTheSameStatefulUpstream() throws Exception {
|
|
void dynamicTargetsRejectEveryForbiddenAddressClass() {
|
|
void errorStatusMapsToTheSameStableException(String transport) throws Exception {
|
|
void everyDocumentedRetryCaseHoldsItsDecision() {
|
|
void everySelectedBlockingTransportRanEveryContract() {
|
|
void everySelectedReactiveTransportRanEveryContract() {
|
|
void everySelectedTransportIsClaimedByExactlyOneContainer() {
|
|
void honoursRetryAfterAndSucceedsOnTheSecondAttempt() throws Exception {
|
|
void methodAndTemplateEncodingIsIdentical(String transport) throws Exception {
|
|
void noProductionPackageDependsOnTheTestkit() {
|
|
void noPublicMethodHandsOutASpringHttpClient() {
|
|
void noPublicMethodReturnsANativeEngineClient() {
|
|
void notSentConnectFailureHasSameStableMetadata(String transport) {
|
|
void observationVocabularyRejectsHighCardinalityAndSecrets() {
|
|
void oversizedStreamingResponseIsRejected() throws Exception {
|
|
void profileDependsOnlyOnTheCoreApi() {
|
|
void publicApiDoesNotExposeNativeEnginesOrUnsafeBuilders() {
|
|
void recordedMetersCarryNoUrlOrCredential() throws Exception {
|
|
void replaysOnceAfterA401AndReResolvesTheCredential() throws Exception {
|
|
void resilienceOwnsRetryDecisionsWithoutDependingOnTransports() {
|
|
void springHttpClientsAreConfinedToTheirExecutionPackages() {
|
|
void stopsAtTheConfiguredAttemptCeilingAgainstAPermanentlyFailingUpstream() throws Exception {
|
|
void theApacheClassicClientCannotSpeakPriorKnowledgeH2c() throws Exception {
|
|
void theCoreApiCarriesNoMutableBuilderSurface() {
|
|
void theExperimentalHttp3PackageIsNotReferencedByStableCode() {
|
|
void theJdkTransportNegotiatesHttp2WhenTheProfileAsksForIt() throws Exception {
|
|
void theJdkTransportStaysOnHttp11WhenTheProfileSaysSo() throws Exception {
|
|
void theReactorTransportNegotiatesHttp2() throws Exception {
|
|
void theSelectedTransportSetIsExplicit() {
|
|
void theSpring7ServiceGroupApiIsUsedOnlyInItsOwnPackage() {
|
|
exit=0
|
|
|