Files
document-haness/docs/clean-architecture-backend-template/final/evidence/rendered/a06-f020-tls-stable-probe.svg
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

171 lines
15 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1822" viewBox="0 0 1200 1822" role="img">
<title>terminal evidence</title>
<desc>Terminal-style rendering generated from retained command output. Sensitive-looking values are redacted in the visual asset.</desc>
<rect x="1" y="1" width="1198" height="1820" rx="14" fill="#0d1117" stroke="#30363d"/>
<rect x="1" y="1" width="1198" height="44" rx="14" fill="#161b22"/>
<rect x="1" y="30" width="1198" height="14" fill="#161b22"/>
<circle cx="24" cy="22" r="6" fill="#ff5f57"/>
<circle cx="44" cy="22" r="6" fill="#febc2e"/>
<circle cx="64" cy="22" r="6" fill="#28c840"/>
<text x="92" y="27" fill="#8b949e" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace">terminal evidence</text>
<text x="24" y="68" fill="#c9d1d9" font-size="15" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">$ set -e
D=$(mktemp -d)
trap &#x27;rm -rf &quot;$D&quot;&#x27; EXIT
printf %s &#x27;&lt;configuration&gt;&lt;root level=&quot;OFF&quot;/&gt;&lt;/configuration&gt;&#x27; &gt; &quot;$D/lo</tspan><tspan x="24" dy="22">gback-off.xml&quot;
cat &gt; &quot;$D/TlsReachProbe.java&quot; &lt;&lt;&#x27;JAVA&#x27;
import com.mongodb.MongoClientSettings;
import dev.caskeleton.ada</tspan><tspan x="24" dy="22">pter.outbound.mongo.autoconfigure.MongoPlatformSettings;
import dev.caskeleton.adapter.outbound.mongo.autoconfigure.Mong</tspan><tspan x="24" dy="22">oProfileProperties;
import dev.caskeleton.adapter.outbound.mongo.client.MongoClientSettingsFactory;
import dev.caskeleto</tspan><tspan x="24" dy="22">n.adapter.outbound.mongo.security.MongoCredentialReference;
import dev.caskeleton.adapter.outbound.mongo.security.MongoC</tspan><tspan x="24" dy="22">redentialResolver;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.simple.Simpl</tspan><tspan x="24" dy="22">eMeterRegistry;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.func</tspan><tspan x="24" dy="22">tion.Function;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.mongodb</tspan><tspan x="24" dy="22">.autoconfigure.MongoAutoConfiguration;
import org.springframework.boot.mongodb.autoconfigure.MongoClientSettingsBuilderC</tspan><tspan x="24" dy="22">ustomizer;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework</tspan><tspan x="24" dy="22">.boot.test.context.runner.ApplicationContextRunner;
public final class TlsReachProbe {
/** The URI the module README</tspan><tspan x="24" dy="22"> shows a deployment using. */
private static final String README_URI = &quot;mongodb://canary-host:31337/portfolio&quot;;
pri</tspan><tspan x="24" dy="22">vate static final ApplicationContextRunner BASE =
new ApplicationContextRunner()
.withConfiguration(Auto</tspan><tspan x="24" dy="22">Configurations.of(
dev.caskeleton.adapter.outbound.mongo.MongoRootAutoConfiguration.class,
M</tspan><tspan x="24" dy="22">ongoAutoConfiguration.class))
.withBean(MeterRegistry.class, SimpleMeterRegistry::new)
.withProperty</tspan><tspan x="24" dy="22">Values(&quot;ca-skeleton.persistence-mongo.enabled=true&quot;);
public static void main(String[] args) {
System.out.println</tspan><tspan x="24" dy="22">(&quot;[속성 키] 같은 URI 를 두 키로 각각 넣어 본다&quot;);
hosts(&quot;spring.mongodb.uri &quot;, &quot;spring.mongodb.uri=&quot; + README_UR</tspan><tspan x="24" dy="22">I);
hosts(&quot;spring.data.mongodb.uri&quot;, &quot;spring.data.mongodb.uri=&quot; + README_URI);
System.out.println();
MongoP</tspan><tspan x="24" dy="22">rofileProperties profile = MongoProfileProperties.production(&quot;secret:[REDACTED];
MongoPlatformSettings settings =
</tspan><tspan x="24" dy="22"> new MongoPlatformSettings(Map.of(&quot;default&quot;, profile), false, false, null);
System.out.println(&quot;[선언] producti</tspan><tspan x="24" dy="22">on 프로파일이 말하는 값&quot;);
System.out.println(&quot; tlsRequired=&quot; + profile.tlsRequired()
+ &quot; stableApiStrict=</tspan><tspan x="24" dy="22">&quot; + profile.stableApiStrict()
+ &quot; connectTimeout=&quot; + profile.connectTimeout().toMillis() + &quot;ms&quot;
+ &quot; se</tspan><tspan x="24" dy="22">rverSelection=&quot; + profile.serverSelectionTimeout().toMillis() + &quot;ms&quot;
+ &quot; poolMax=&quot; + profile.poolMaxSize()
</tspan><tspan x="24" dy="22"> + &quot; uuid=&quot; + profile.uuidRepresentation());
settings.validate();
System.out.println(&quot; 이 선언을 설정 검증</tspan><tspan x="24" dy="22">이 통과시킨다&quot;);
System.out.println();
System.out.println(&quot;[실제] Boot 가 클라이언트를 만들 때 쓰는 설정 (커스</tspan><tspan x="24" dy="22">터마이저 적용 후)&quot;);
BASE.withPropertyValues(&quot;spring.mongodb.uri=&quot; + README_URI)
.run(context -&gt; {
</tspan><tspan x="24" dy="22"> System.out.println(&quot; 커스터마이저 &quot; + customizers(context).size() + &quot;개 적용&quot;);
print(&quot; &quot;, effective(contex</tspan><tspan x="24" dy="22">t));
});
System.out.println();
System.out.println(&quot;[다른 경로] URI 를 건드리지 않고 ssl 속성만 켠다&quot;);
</tspan><tspan x="24" dy="22"> BASE.withPropertyValues(&quot;spring.mongodb.uri=&quot; + README_URI, &quot;spring.mongodb.ssl.enabled=true&quot;)
.run(context </tspan><tspan x="24" dy="22">-&gt; System.out.println(
&quot; spring.mongodb.ssl.enabled=true -&gt; sslEnabled=&quot;
+ effective(contex</tspan><tspan x="24" dy="22">t).getSslSettings().isEnabled()));
System.out.println();
System.out.println(&quot;[미도달] 팩토리가 만들었을 설정&quot;);</tspan><tspan x="24" dy="22">
print(&quot; &quot;, new MongoClientSettingsFactory(settings, &quot;default&quot;, resolver()).create());
System.exit(0);
}
p</tspan><tspan x="24" dy="22">rivate static void hosts(String label, String property) {
BASE.withPropertyValues(property)
.run(context -&gt; </tspan><tspan x="24" dy="22">System.out.println(
&quot; &quot; + label + &quot; -&gt; &quot; + effective(context).getClusterSettings().getHosts()));
}
pri</tspan><tspan x="24" dy="22">vate static List&lt;MongoClientSettingsBuilderCustomizer&gt; customizers(
AssertableApplicationContext context) {
re</tspan><tspan x="24" dy="22">turn context.getBeanProvider(MongoClientSettingsBuilderCustomizer.class)
.orderedStream().toList();
}
/** W</tspan><tspan x="24" dy="22">hat Boot hands the driver: the base bean with every customizer applied, as Boot does. */
private static MongoClientSet</tspan><tspan x="24" dy="22">tings effective(AssertableApplicationContext context) {
MongoClientSettings.Builder builder =
MongoClientSet</tspan><tspan x="24" dy="22">tings.builder(context.getBean(MongoClientSettings.class));
customizers(context).forEach(customizer -&gt; customizer.cus</tspan><tspan x="24" dy="22">tomize(builder));
return builder.build();
}
private static void print(String indent, MongoClientSettings built)</tspan><tspan x="24" dy="22"> {
System.out.println(indent + &quot;sslEnabled=&quot; + built.getSslSettings().isEnabled());
System.out.println(indent + </tspan><tspan x="24" dy="22">&quot;connectTimeoutMs=&quot;
+ built.getSocketSettings().getConnectTimeout(TimeUnit.MILLISECONDS)
+ &quot; serverSele</tspan><tspan x="24" dy="22">ctionTimeoutMs=&quot;
+ built.getClusterSettings().getServerSelectionTimeout(TimeUnit.MILLISECONDS));
System.out.</tspan><tspan x="24" dy="22">println(indent + &quot;poolMaxSize=&quot; + built.getConnectionPoolSettings().getMaxSize()
+ &quot; serverApi=&quot; + built.getSer</tspan><tspan x="24" dy="22">verApi()
+ &quot; uuidRepresentation=&quot; + built.getUuidRepresentation());
}
private static MongoCredentialResolv</tspan><tspan x="24" dy="22">er resolver() {
return new MongoCredentialResolver() {
@Override
public &lt;T&gt; T withConnectionString(
</tspan><tspan x="24" dy="22"> MongoCredentialReference reference, Function&lt;String, T&gt; use) {
return use.apply(README_URI);
}
}</tspan><tspan x="24" dy="22">;
}
}
JAVA
CACHE=/root/.gradle/caches/modules-2/files-2.1
LOCK=src/adapter/outbound/persistence-mongo/gradle.lockfile</tspan><tspan x="24" dy="22">
CP=src/adapter/outbound/persistence-mongo/build/classes/java/main
while IFS= read -r line; do
coord=${line%%=*}; conf</tspan><tspan x="24" dy="22">s=${line#*=}
case &quot;,$confs,&quot; in *&quot;,testRuntimeClasspath,&quot;*) ;; *) continue ;; esac
g=${coord%%:*}; r=${coord#*:}; n=</tspan><tspan x="24" dy="22">${r%%:*}; v=${r##*:}
jar=$(find &quot;$CACHE/$g/$n/$v&quot; -name &#x27;*.jar&#x27; ! -name &#x27;*sources*&#x27; ! -name &#x27;*javadoc*&#x27; 2&gt;/dev/null | </tspan><tspan x="24" dy="22">head -1)
[ -n &quot;$jar&quot; ] &amp;&amp; CP=&quot;$CP:$jar&quot;
done &lt; &lt;(grep -E &#x27;^[a-zA-Z0-9._-]+:[^=]+=&#x27; &quot;$LOCK&quot;)
javac -encoding UTF-8 -cp</tspan><tspan x="24" dy="22"> &quot;$CP&quot; -d &quot;$D&quot; &quot;$D/TlsReachProbe.java&quot;
java -XX:+EnableDynamicAgentLoading -Dlogback.configurationFile=&quot;$D/logback-off.x</tspan><tspan x="24" dy="22">ml&quot; \
-Dstdout.encoding=UTF-8 -cp &quot;$D:$CP&quot; TlsReachProbe 2&gt;/dev/null</tspan></text>
<text x="24" y="1256" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">cwd: /shared/codebase/clean-architecture-backend-template</tspan></text>
<text x="24" y="1278" fill="#8b949e" font-size="13" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">time: 2026-09-02T08:41:57+00:00 · exit 0</tspan></text>
<line x1="24" y1="1292" x2="1176" y2="1292" stroke="#30363d"/>
<text x="24" y="1332" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[속성 키] 같은 URI 를 두 키로 각각 넣어 본다</tspan></text>
<text x="24" y="1354" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> spring.mongodb.uri -&gt; [canary-host:31337]</tspan></text>
<text x="24" y="1376" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> spring.data.mongodb.uri -&gt; [localhost:27017]</tspan></text>
<text x="24" y="1398" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1420" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[선언] production 프로파일이 말하는 값</tspan></text>
<text x="24" y="1442" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> tlsRequired=true stableApiStrict=true connectTimeout=2000ms serverSelection=3000ms poolMax=40 uuid=STANDARD</tspan></text>
<text x="24" y="1464" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 이 선언을 설정 검증이 통과시킨다</tspan></text>
<text x="24" y="1486" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1508" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[실제] Boot 가 클라이언트를 만들 때 쓰는 설정 (커스터마이저 적용 후)</tspan></text>
<text x="24" y="1530" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 커스터마이저 2개 적용</tspan></text>
<text x="24" y="1552" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> sslEnabled=false</tspan></text>
<text x="24" y="1574" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> connectTimeoutMs=10000 serverSelectionTimeoutMs=30000</tspan></text>
<text x="24" y="1596" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> poolMaxSize=100 serverApi=null uuidRepresentation=UNSPECIFIED</tspan></text>
<text x="24" y="1618" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1640" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[다른 경로] URI 를 건드리지 않고 ssl 속성만 켠다</tspan></text>
<text x="24" y="1662" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> spring.mongodb.ssl.enabled=true -&gt; sslEnabled=true</tspan></text>
<text x="24" y="1684" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"></tspan></text>
<text x="24" y="1706" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[미도달] 팩토리가 만들었을 설정</tspan></text>
<text x="24" y="1728" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> sslEnabled=true</tspan></text>
<text x="24" y="1750" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> connectTimeoutMs=2000 serverSelectionTimeoutMs=3000</tspan></text>
<text x="24" y="1772" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> poolMaxSize=40 serverApi=ServerApi{version=V1, deprecationErrors=true, strict=true} uuidRepresentation=STANDARD</tspan></text>
</svg>