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>
147 lines
12 KiB
XML
147 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">W=$C/src/adapter/inbound/web
|
|
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
|
java -version 2>&1 | head -1
|
|
cp=""
|
|
while IFS</tspan><tspan x="24" dy="22">= read -r line; do
|
|
coord=${line%%=*}; confs=${line#*=}
|
|
case ",$confs," in *",runtimeClasspath,"*) ;; *) continue ;; </tspan><tspan x="24" dy="22">esac
|
|
g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name '*.jar' ! -na</tspan><tspan x="24" dy="22">me '*sources*' ! -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">._-]+:[^=]+=' "$W/gradle.lockfile")
|
|
CP=$(find "$W/build/libs" -name '*+21234e38cdb9.jar' | tr '\n' ':')${cp#:}
|
|
cat > "$D</tspan><tspan x="24" dy="22">/AlreadySafeProbe.java" <<'JAVA'
|
|
import dev.caskeleton.adapter.inbound.web.error.WebProblemSanitizer;
|
|
import java.util.L</tspan><tspan x="24" dy="22">inkedHashMap;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
|
|
/** alreadySafe 의 삼항이 어느 가지를 타는지 입력마다 직접</tspan><tspan x="24" dy="22"> 본다. */
|
|
public final class AlreadySafeProbe {
|
|
|
|
/** 제어 문자를 소스에 직접 적지 않으려고 코드 점으로 만든다. */
|
|
pr</tspan><tspan x="24" dy="22">ivate static String ch(int code) {
|
|
return String.valueOf((char) code);
|
|
}
|
|
|
|
/** 메서드 본문의 삼항 조건 그대로. */</tspan><tspan x="24" dy="22">
|
|
private static boolean takesRedactedBranch(String input) {
|
|
return input.trim().toLowerCase(Locale.ROOT).isEmpty()</tspan><tspan x="24" dy="22">;
|
|
}
|
|
|
|
private static String show(String value) {
|
|
StringBuilder shown = new StringBuilder();
|
|
for (char each : </tspan><tspan x="24" dy="22">value.toCharArray()) {
|
|
shown.append(each < 0x20 || each > 0x7e ? String.format("U+%04X", (int) each) : each);
|
|
</tspan><tspan x="24" dy="22">}
|
|
return shown.toString();
|
|
}
|
|
|
|
/** 두 판정이 갈리는 코드 점을 전 범위에서 센다. 예시 몇 개로 "틈"을 말할 수 없다.</tspan><tspan x="24" dy="22"> */
|
|
private static void sweep(WebProblemSanitizer sanitizer) {
|
|
int gap = 0;
|
|
int returnsTrue = 0;
|
|
int first</tspan><tspan x="24" dy="22"> = -1;
|
|
int last = -1;
|
|
StringBuilder ranges = new StringBuilder();
|
|
boolean inRun = false;
|
|
for (int code =</tspan><tspan x="24" dy="22"> 0; code <= 0xFFFF; code++) {
|
|
String value = ch(code);
|
|
boolean inGap = !value.isBlank() && value.trim().isEm</tspan><tspan x="24" dy="22">pty();
|
|
if (inGap) {
|
|
gap++;
|
|
if (sanitizer.alreadySafe(value, 200)) {
|
|
returnsTrue++;
|
|
</tspan><tspan x="24" dy="22"> }
|
|
if (!inRun) {
|
|
first = code;
|
|
inRun = true;
|
|
}
|
|
last = code;
|
|
} else i</tspan><tspan x="24" dy="22">f (inRun) {
|
|
ranges.append(ranges.length() == 0 ? "" : ", ")
|
|
.append(String.format("U+%04X..U+%04X", </tspan><tspan x="24" dy="22">first, last));
|
|
inRun = false;
|
|
}
|
|
}
|
|
if (inRun) {
|
|
ranges.append(ranges.length() == 0 ? "" : ", </tspan><tspan x="24" dy="22">")
|
|
.append(String.format("U+%04X..U+%04X", first, last));
|
|
}
|
|
System.out.println();
|
|
System.out.print</tspan><tspan x="24" dy="22">ln("[두 판정이 갈리는 코드 점 — 기본 다국어 평면 전체를 훑는다]");
|
|
System.out.printf(" %-22s %d%n", "공백은 아닌데</tspan><tspan x="24" dy="22"> 다듬기면 비는 것", gap);
|
|
System.out.printf(" %-22s %s%n", "그 구간", ranges);
|
|
System.out.printf(" %-22s %d%n"</tspan><tspan x="24" dy="22">, "그중 alreadySafe 가 참", returnsTrue);
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
WebProblemSanitizer saniti</tspan><tspan x="24" dy="22">zer = new WebProblemSanitizer();
|
|
Map<String, String> inputs = new LinkedHashMap<>();
|
|
inputs.put("보통 문장", "th</tspan><tspan x="24" dy="22">e request was rejected");
|
|
inputs.put("앞뒤 공백", " spaced ");
|
|
inputs.put("널 문자", ch(0));
|
|
inputs.put("</tspan><tspan x="24" dy="22">널 문자 셋", ch(0) + ch(0) + ch(0));
|
|
inputs.put("백스페이스", ch(8));
|
|
inputs.put("탈출 문자", ch(27));
|
|
input</tspan><tspan x="24" dy="22">s.put("삭제 문자", ch(127));
|
|
inputs.put("보통 공백만", " ");
|
|
inputs.put("줄 구분자", ch(0x2028));
|
|
inputs.p</tspan><tspan x="24" dy="22">ut("붙임 공백", ch(0x00A0));
|
|
|
|
System.out.println();
|
|
System.out.printf(" %-14s %-14s %-9s %-11s %-14s %s%n",
|
|
</tspan><tspan x="24" dy="22"> "입력", "보인 값", "isBlank", "trim 이 빔", "편집됨 가지", "alreadySafe");
|
|
for (var entry : inputs.entrySet()) </tspan><tspan x="24" dy="22">{
|
|
String value = entry.getValue();
|
|
boolean blank = value.isBlank();
|
|
boolean trimmedEmpty = takesRedact</tspan><tspan x="24" dy="22">edBranch(value);
|
|
String branch = blank ? "선행에서 끊김" : (trimmedEmpty ? "탄다" : "안 탄다");
|
|
System.out.p</tspan><tspan x="24" dy="22">rintf(" %-14s %-14s %-9s %-11s %-14s %s%n",
|
|
entry.getKey(), show(value), blank, trimmedEmpty, branch,
|
|
</tspan><tspan x="24" dy="22"> sanitizer.alreadySafe(value, 200));
|
|
}
|
|
sweep(sanitizer);
|
|
}
|
|
}
|
|
JAVA
|
|
javac -encoding UTF-8 -cp "$CP" -d "$D" "$</tspan><tspan x="24" dy="22">D/AlreadySafeProbe.java"
|
|
java -Dstdout.encoding=UTF-8 -cp "$CP:$D" AlreadySafeProbe
|
|
</tspan></text>
|
|
<text x="24" y="860" 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="882" 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-02T19:53:40+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="896" x2="1176" y2="896" stroke="#30363d"/>
|
|
<text x="24" y="936" 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="958" 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="980" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 입력 보인 값 isBlank trim 이 빔 편집됨 가지 alreadySafe</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"> 보통 문장 the request was rejected false false 안 탄다 true</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"> 앞뒤 공백 spaced false false 안 탄다 true</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"> 널 문자 U+0000 false true 탄다 true</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"> 널 문자 셋 U+0000U+0000U+0000 false true 탄다 true</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"> 백스페이스 U+0008 false true 탄다 true</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"> 탈출 문자 U+001B false true 탄다 true</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"> 삭제 문자 U+007F false false 안 탄다 false</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"> 보통 공백만 true true 선행에서 끊김 false</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"> 줄 구분자 U+2028 true false 선행에서 끊김 false</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"> 붙임 공백 U+00A0 false false 안 탄다 true</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">[두 판정이 갈리는 코드 점 — 기본 다국어 평면 전체를 훑는다]</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"> 공백은 아닌데 다듬기면 비는 것 23</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"> 그 구간 U+0000..U+0008, U+000E..U+001B</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"> 그중 alreadySafe 가 참 23</tspan></text>
|
|
</svg>
|