Files
document-haness/docs/clean-architecture-backend-template/final/evidence/rendered/a14-f011-maxarrayelements-converter.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

159 lines
14 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1734" viewBox="0 0 1200 1734" 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="1732" 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
set -o pipefail
D=$(mktemp -d); trap &#x27;rm -rf &quot;$D&quot;&#x27; EXIT
C=/shared/codebase/clean-architecture-backend-template
</tspan><tspan x="24" dy="22">M=$C/src/adapter/inbound/web
CACHE=/root/.gradle/caches/modules-2/files-2.1
java -version 2&gt;&amp;1 | grep version
cp=&quot;&quot;
whil</tspan><tspan x="24" dy="22">e IFS= read -r line; do
coord=${line%%=*}; confs=${line#*=}
case &quot;,$confs,&quot; in *&quot;,testRuntimeClasspath,&quot;*) ;; *) con</tspan><tspan x="24" dy="22">tinue ;; esac
g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}
jar=$(find &quot;$CACHE/$g/$n/$v&quot; -name &#x27;*.j</tspan><tspan x="24" dy="22">ar&#x27; ! -name &#x27;*sources*&#x27; ! -name &#x27;*javadoc*&#x27; 2&gt;/dev/null | head -1) || true
[ -n &quot;$jar&quot; ] &amp;&amp; cp=&quot;$cp:$jar&quot;
done &lt; &lt;(gre</tspan><tspan x="24" dy="22">p -E &#x27;^[a-zA-Z0-9._-]+:[^=]+=&#x27; &quot;$M/gradle.lockfile&quot;)
SIB=$(find &quot;$C/src&quot; -path &#x27;*/build/libs/*+21234e38cdb9*.jar&#x27; ! -nam</tspan><tspan x="24" dy="22">e &#x27;*-testkit.jar&#x27; | sort | tr &#x27;\n&#x27; &#x27;:&#x27;)
cat &gt; &quot;$D/ConverterMapperProbe.java&quot; &lt;&lt;&#x27;JAVA&#x27;
import java.util.Map;
import org.s</tspan><tspan x="24" dy="22">pringframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTe</tspan><tspan x="24" dy="22">st;
import org.springframework.http.converter.HttpMessageConverter;
import tools.jackson.databind.ObjectMapper;
import t</tspan><tspan x="24" dy="22">ools.jackson.databind.json.JsonMapper;
/** 이 저장소의 엄격 매퍼가 실제로 요청 본문을 읽는 컨버터에 도달하는지 본다. */</tspan><tspan x="24" dy="22">
public final class ConverterMapperProbe {
/** 엄격 프로파일이 세운 파서인지 두 값으로 가른다. */
private static St</tspan><tspan x="24" dy="22">ring shape(ObjectMapper mapper) {
var c = mapper.tokenStreamFactory().streamReadConstraints();
return &quot;maxNestin</tspan><tspan x="24" dy="22">gDepth=&quot; + c.getMaxNestingDepth()
+ &quot; maxStringLength=&quot; + c.getMaxStringLength();
}
public static void main</tspan><tspan x="24" dy="22">(String[] args) throws Exception {
var runner = new org.springframework.boot.test.context.runner.WebApplicationConte</tspan><tspan x="24" dy="22">xtRunner()
.withConfiguration(AutoConfigurations.of(
Class.forName(&quot;org.springframework.boot.jackson</tspan><tspan x="24" dy="22">.autoconfigure.JacksonAutoConfiguration&quot;),
Class.forName(&quot;org.springframework.boot.http.converter.autoconfig</tspan><tspan x="24" dy="22">ure.HttpMessageConvertersAutoConfiguration&quot;),
Class.forName(&quot;org.springframework.boot.webmvc.autoconfigure.W</tspan><tspan x="24" dy="22">ebMvcAutoConfiguration&quot;),
Class.forName(&quot;dev.caskeleton.adapter.inbound.web.mvc.autoconfigure.WebMvcPlatform</tspan><tspan x="24" dy="22">AutoConfiguration&quot;)));
var withoutRepo = new org.springframework.boot.test.context.runner.WebApplicationContextRunn</tspan><tspan x="24" dy="22">er()
.withConfiguration(AutoConfigurations.of(
Class.forName(&quot;org.springframework.boot.jackson.autoc</tspan><tspan x="24" dy="22">onfigure.JacksonAutoConfiguration&quot;),
Class.forName(&quot;org.springframework.boot.http.converter.autoconfigure.Ht</tspan><tspan x="24" dy="22">tpMessageConvertersAutoConfiguration&quot;),
Class.forName(&quot;org.springframework.boot.webmvc.autoconfigure.WebMvcA</tspan><tspan x="24" dy="22">utoConfiguration&quot;)));
runner.run(context -&gt; {
System.out.println();
System.out.println(&quot;[컨텍스트에 있</tspan><tspan x="24" dy="22">는 JsonMapper 빈]&quot;);
Map&lt;String, JsonMapper&gt; mappers = context.getBeansOfType(JsonMapper.class);
for (var en</tspan><tspan x="24" dy="22">try : mappers.entrySet()) {
boolean primary = context.getBeanFactory()
.getBeanDefinition(entry.getK</tspan><tspan x="24" dy="22">ey()).isPrimary();
System.out.printf(&quot; %-26s %s%s%n&quot;, entry.getKey(), shape(entry.getValue()),
prim</tspan><tspan x="24" dy="22">ary ? &quot; (primary)&quot; : &quot;&quot;);
}
System.out.println();
System.out.println(&quot;[본문을 읽는 컨버터가 실제로 든</tspan><tspan x="24" dy="22"> 매퍼]&quot;);
var adapter = context.getBean(
org.springframework.web.servlet.mvc.method.annotation.RequestMa</tspan><tspan x="24" dy="22">ppingHandlerAdapter.class);
for (HttpMessageConverter&lt;?&gt; converter : adapter.getMessageConverters()) {
if </tspan><tspan x="24" dy="22">(!converter.getClass().getSimpleName().contains(&quot;Jackson&quot;)) {
continue;
}
// 컨버터가 실제로 </tspan><tspan x="24" dy="22">들고 있는 매퍼를 필드에서 꺼낸다. 접근자 이름이 판마다 다르다.
for (Class&lt;?&gt; type = converter.getClass(); type !</tspan><tspan x="24" dy="22">= null; type = type.getSuperclass()) {
for (var field : type.getDeclaredFields()) {
if (!ObjectMap</tspan><tspan x="24" dy="22">per.class.isAssignableFrom(field.getType())) {
continue;
}
field.setAccessible(tru</tspan><tspan x="24" dy="22">e);
Object held = field.get(converter);
if (held != null) {
System.out.println(&quot; </tspan><tspan x="24" dy="22">&quot; + converter.getClass().getSimpleName()
+ &quot; &quot; + shape((ObjectMapper) held));
}
</tspan><tspan x="24" dy="22"> }
}
}
System.out.println();
System.out.println(&quot;[엄격 프로파일이 세우는 파서]&quot;);
var st</tspan><tspan x="24" dy="22">rict = dev.caskeleton.adapter.inbound.web.json.BoundedJsonFactory.create(
dev.caskeleton.adapter.inbound.web.j</tspan><tspan x="24" dy="22">son.WebJsonProfile.strict());
var sc = strict.streamReadConstraints();
System.out.println(&quot; maxNestingDepth</tspan><tspan x="24" dy="22">=&quot; + sc.getMaxNestingDepth()
+ &quot; maxStringLength=&quot; + sc.getMaxStringLength());
});
// 대조군. 이 저장</tspan><tspan x="24" dy="22">소 자동설정만 빼면 무엇이 오는지 본다.
withoutRepo.run(context -&gt; {
System.out.println();
System.out.pri</tspan><tspan x="24" dy="22">ntln(&quot;[이 저장소 자동설정을 뺀 같은 컨텍스트]&quot;);
for (var entry : context.getBeansOfType(JsonMapper.class).entrySe</tspan><tspan x="24" dy="22">t()) {
System.out.printf(&quot; %-26s %s%n&quot;, entry.getKey(), shape(entry.getValue()));
}
var adapter = c</tspan><tspan x="24" dy="22">ontext.getBean(
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.class);
</tspan><tspan x="24" dy="22"> for (HttpMessageConverter&lt;?&gt; converter : adapter.getMessageConverters()) {
if (!converter.getClass().getSimpl</tspan><tspan x="24" dy="22">eName().equals(&quot;JacksonJsonHttpMessageConverter&quot;)) {
continue;
}
for (Class&lt;?&gt; type = converte</tspan><tspan x="24" dy="22">r.getClass(); type != null; type = type.getSuperclass()) {
for (var field : type.getDeclaredFields()) {
</tspan><tspan x="24" dy="22"> if (!ObjectMapper.class.isAssignableFrom(field.getType())) {
continue;
}
fie</tspan><tspan x="24" dy="22">ld.setAccessible(true);
Object held = field.get(converter);
if (held != null) {
Sy</tspan><tspan x="24" dy="22">stem.out.println(&quot; &quot; + converter.getClass().getSimpleName()
+ &quot; &quot; + shape((ObjectMapper) held));
</tspan><tspan x="24" dy="22"> }
}
}
}
});
}
}
JAVA
javac -encoding UTF-8 -cp &quot;$SIB$cp&quot; -d &quot;$D/out&quot; &quot;$D/Converte</tspan><tspan x="24" dy="22">rMapperProbe.java&quot; 2&gt;&amp;1 | head -20
printf &#x27;%s\n&#x27; &#x27;&lt;configuration&gt;&lt;root level=&quot;OFF&quot;/&gt;&lt;/configuration&gt;&#x27; &gt; &quot;$D/out/logback.</tspan><tspan x="24" dy="22">xml&quot;
java -Dstdout.encoding=UTF-8 -cp &quot;$D/out:$SIB$cp&quot; ConverterMapperProbe
</tspan></text>
<text x="24" y="1212" 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/src</tspan></text>
<text x="24" y="1234" 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-03T01:52:47+00:00 · exit 0</tspan></text>
<line x1="24" y1="1248" x2="1176" y2="1248" stroke="#30363d"/>
<text x="24" y="1288" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">openjdk version &quot;21.0.12&quot; 2026-07-21</tspan></text>
<text x="24" y="1310" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">warning: unknown enum constant Status.STABLE</tspan></text>
<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"> reason: class file for org.apiguardian.api.API$Status not found</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">1 warning</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"></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">[컨텍스트에 있는 JsonMapper 빈]</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"> webStrictObjectMapper maxNestingDepth=64 maxStringLength=1048576</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"></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"> JacksonJsonHttpMessageConverter maxNestingDepth=64 maxStringLength=1048576</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"> JacksonCborHttpMessageConverter maxNestingDepth=500 maxStringLength=100000000</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"> JacksonXmlHttpMessageConverter maxNestingDepth=500 maxStringLength=100000000</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"></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">[엄격 프로파일이 세우는 파서]</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"> maxNestingDepth=64 maxStringLength=1048576</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">[이 저장소 자동설정을 뺀 같은 컨텍스트]</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"> jacksonJsonMapper maxNestingDepth=500 maxStringLength=100000000</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"> JacksonJsonHttpMessageConverter maxNestingDepth=500 maxStringLength=100000000</tspan></text>
</svg>