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>
125 lines
12 KiB
XML
125 lines
12 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1360" viewBox="0 0 1200 1360" 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="1358" 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 'rm -rf "$D"' EXIT
|
|
C=/shared/codebase/clean-architecture-backend-template
|
|
</tspan><tspan x="24" dy="22">M=$C/src/app-bootstrap
|
|
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
|
java -version 2>&1 | head -1
|
|
cp=""
|
|
while IFS= read</tspan><tspan x="24" dy="22"> -r line; do
|
|
coord=${line%%=*}; confs=${line#*=}
|
|
case ",$confs," in *",runtimeClasspath,"*) ;; *) continue ;; esac
|
|
</tspan><tspan x="24" dy="22"> g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name '*.jar' ! -name '*s</tspan><tspan x="24" dy="22">ources*' ! -name '*javadoc*' 2>/dev/null | head -1)
|
|
[ -n "$jar" ] && cp="$cp:$jar"
|
|
done < <(grep -E '^[a-zA-Z0-9._-]+:</tspan><tspan x="24" dy="22">[^=]+=' "$M/gradle.lockfile")
|
|
CP=${cp#:}
|
|
cat > "$D/ManagementPortProbe.java" <<'JAVA'
|
|
package org.springframework.boot.a</tspan><tspan x="24" dy="22">ctuate.autoconfigure.web.server;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Method;
|
|
import org.spri</tspan><tspan x="24" dy="22">ngframework.core.type.AnnotationMetadata;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.Map;
|
|
import org.springframewo</tspan><tspan x="24" dy="22">rk.core.env.MapPropertySource;
|
|
import org.springframework.core.env.StandardEnvironment;
|
|
|
|
/** 관리 포트가 같을 때 관리 컨</tspan><tspan x="24" dy="22">텍스트 설정이 어디로 가는지 프레임워크에 직접 물어본다. */
|
|
public final class ManagementPortProbe {
|
|
|
|
private static vo</tspan><tspan x="24" dy="22">id portType(String app, String mgmt) {
|
|
Map<String, Object> values = new LinkedHashMap<>();
|
|
values.put("server.po</tspan><tspan x="24" dy="22">rt", app);
|
|
if (mgmt != null) {
|
|
values.put("management.server.port", mgmt);
|
|
}
|
|
StandardEnvironment env =</tspan><tspan x="24" dy="22"> new StandardEnvironment();
|
|
env.getPropertySources().addFirst(new MapPropertySource("probe", values));
|
|
System.ou</tspan><tspan x="24" dy="22">t.printf(" server.port=%-6s management.server.port=%-10s %s%n",
|
|
app, mgmt == null ? "(없음)" : mgmt, Management</tspan><tspan x="24" dy="22">PortType.get(env));
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
System.out.println();
|
|
Syste</tspan><tspan x="24" dy="22">m.out.println("[출하값과 두 포트를 같게 둔 값으로 ManagementPortType 을 부른다]");
|
|
portType("8080", "9001");
|
|
por</tspan><tspan x="24" dy="22">tType("8080", "8080");
|
|
portType("8080", "");
|
|
portType("8080", null);
|
|
|
|
System.out.println();
|
|
System.out.p</tspan><tspan x="24" dy="22">rintln("[SAME 일 때 부트가 켜는 설정과 그 설정이 고르는 타입]");
|
|
Class<?> same = Class.forName(
|
|
"org.springf</tspan><tspan x="24" dy="22">ramework.boot.actuate.autoconfigure.web.server"
|
|
+ ".ManagementContextAutoConfiguration$SameManagementContext</tspan><tspan x="24" dy="22">Configuration");
|
|
Class<?> enabler = Class.forName(same.getName() + "$EnableSameManagementContextConfiguration");
|
|
</tspan><tspan x="24" dy="22"> System.out.println(" " + same.getSimpleName() + " 조건 : "
|
|
+ same.getAnnotation(ConditionalOnManagementPort.cl</tspan><tspan x="24" dy="22">ass).value());
|
|
System.out.println(" " + enabler.getSimpleName() + " 가 켜는 것 : "
|
|
+ enabler.getAnnotation(</tspan><tspan x="24" dy="22">EnableManagementContext.class).value());
|
|
|
|
System.out.println();
|
|
System.out.println("[그 설정이 고르는 클래스. 조</tspan><tspan x="24" dy="22">건 평가는 이 단계에 없다]");
|
|
Constructor<?> ctor = ManagementContextConfigurationImportSelector.class
|
|
.getDe</tspan><tspan x="24" dy="22">claredConstructor();
|
|
ctor.setAccessible(true);
|
|
Object selector = ctor.newInstance();
|
|
Method select = Managem</tspan><tspan x="24" dy="22">entContextConfigurationImportSelector.class
|
|
.getDeclaredMethod("selectImports", AnnotationMetadata.class);
|
|
s</tspan><tspan x="24" dy="22">elect.setAccessible(true);
|
|
String[] selected = (String[]) select.invoke(selector, AnnotationMetadata.introspect(enab</tspan><tspan x="24" dy="22">ler));
|
|
for (String name : selected) {
|
|
boolean ours = name.startsWith("dev.caskeleton");
|
|
String suffix = </tspan><tspan x="24" dy="22">"";
|
|
if (ours) {
|
|
Object annotation = Class.forName(name).getAnnotation(
|
|
org.springframework.boo</tspan><tspan x="24" dy="22">t.actuate.autoconfigure.web.ManagementContextConfiguration.class);
|
|
suffix = " 등록 타입 " + ((org.springframew</tspan><tspan x="24" dy="22">ork.boot.actuate.autoconfigure.web
|
|
.ManagementContextConfiguration) annotation).value();
|
|
}
|
|
Syste</tspan><tspan x="24" dy="22">m.out.println(" " + (ours ? "*" : " ") + " " + name + suffix);
|
|
}
|
|
}
|
|
}
|
|
JAVA
|
|
# 선택자가 읽을 클래스는 고정 리비전 소</tspan><tspan x="24" dy="22">스에서 직접 컴파일한다. 빌드 산출물은 쓰지 않는다.
|
|
SIB=$(find "$C/src" -path '*/build/libs/*+21234e38cdb9.jar' ! -name '</tspan><tspan x="24" dy="22">*-testkit.jar' | tr '\n' ':')
|
|
javac -encoding UTF-8 -cp "$SIB$CP" -d "$D/out" \
|
|
"$M/src/main/java/dev/caskeleton/boots</tspan><tspan x="24" dy="22">trap/autoconfigure/fileserver/FileserverAdminManagementContextConfiguration.java" \
|
|
"$D/ManagementPortProbe.java" 2>&1</tspan><tspan x="24" dy="22"> | head -5
|
|
java -Dstdout.encoding=UTF-8 -cp "$D/out:$SIB$CP:$M/src/main/resources" \
|
|
org.springframework.boot.actuate.</tspan><tspan x="24" dy="22">autoconfigure.web.server.ManagementPortProbe
|
|
</tspan></text>
|
|
<text x="24" y="882" 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="904" 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-02T22:24:25+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="918" x2="1176" y2="918" stroke="#30363d"/>
|
|
<text x="24" y="958" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">openjdk version "21.0.12" 2026-07-21</tspan></text>
|
|
<text x="24" y="980" 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="1002" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[출하값과 두 포트를 같게 둔 값으로 ManagementPortType 을 부른다]</tspan></text>
|
|
<text x="24" y="1024" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> server.port=8080 management.server.port=9001 DIFFERENT</tspan></text>
|
|
<text x="24" y="1046" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> server.port=8080 management.server.port=8080 SAME</tspan></text>
|
|
<text x="24" y="1068" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> server.port=8080 management.server.port= SAME</tspan></text>
|
|
<text x="24" y="1090" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> server.port=8080 management.server.port=(없음) SAME</tspan></text>
|
|
<text x="24" y="1112" 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="1134" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[SAME 일 때 부트가 켜는 설정과 그 설정이 고르는 타입]</tspan></text>
|
|
<text x="24" y="1156" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> SameManagementContextConfiguration 조건 : SAME</tspan></text>
|
|
<text x="24" y="1178" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> EnableSameManagementContextConfiguration 가 켜는 것 : SAME</tspan></text>
|
|
<text x="24" y="1200" 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="1222" 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="1244" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> org.springframework.boot.security.autoconfigure.actuate.web.servlet.SecurityRequestMatchersManagementContextConfiguration</tspan></text>
|
|
<text x="24" y="1266" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> org.springframework.boot.servlet.autoconfigure.actuate.web.ServletEndpointManagementContextConfiguration</tspan></text>
|
|
<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"> org.springframework.boot.webmvc.autoconfigure.actuate.web.WebMvcEndpointManagementContextConfiguration</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"> * dev.caskeleton.bootstrap.autoconfigure.fileserver.FileserverAdminManagementContextConfiguration 등록 타입 ANY</tspan></text>
|
|
</svg>
|