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>
15 lines
4.3 KiB
JSON
15 lines
4.3 KiB
JSON
{
|
|
"assetKey": "a14-f014-advanced-switches",
|
|
"kind": "terminal",
|
|
"command": "set -e\nset -o pipefail\nD=$(mktemp -d); trap 'rm -rf \"$D\"' EXIT\nC=/shared/codebase/clean-architecture-backend-template\nM=$C/src/adapter/inbound/web\nCACHE=/root/.gradle/caches/modules-2/files-2.1\njava -version 2>&1 | grep version\ncp=\"\"\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",testRuntimeClasspath,\"*) ;; *) continue ;; esac\n g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}\n jar=$(find \"$CACHE/$g/$n/$v\" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1) || true\n [ -n \"$jar\" ] && cp=\"$cp:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$M/gradle.lockfile\")\nSIB=$(find \"$C/src\" -path '*/build/libs/*+21234e38cdb9*.jar' ! -name '*-testkit.jar' | sort | tr '\\n' ':')\ncat > \"$D/AdvancedSwitchProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.inbound.web.advanced.WebAdvancedFeature;\nimport java.io.IOException;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.Files;\nimport java.nio.file.Path;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Stream;\n\n/** 열거형이 발행하는 속성 이름마다, main 소스에 그 이름을 읽는 자리가 있는지 센다. */\npublic final class AdvancedSwitchProbe {\n\n private static final Path SRC = Path.of(\"/shared/codebase/clean-architecture-backend-template/src\");\n\n /** main 소스 파일 전부. 시험과 빌드 산출물은 뺀다. */\n private static List<Path> mainSources() throws IOException {\n try (Stream<Path> walk = Files.walk(SRC)) {\n return walk.filter(Files::isRegularFile)\n .filter(p -> {\n String s = p.toString();\n return s.contains(\"/src/main/\") && !s.contains(\"/build/\")\n && (s.endsWith(\".java\") || s.endsWith(\".yml\") || s.endsWith(\".yaml\")\n || s.endsWith(\".properties\"));\n })\n .toList();\n }\n }\n\n /** 속성 이름의 접두사까지만 읽는 조건도 세도록, .enabled 를 뗀 형태로도 찾는다. */\n private static List<String> readers(List<Path> files, String property) throws IOException {\n String prefix = property.endsWith(\".enabled\")\n ? property.substring(0, property.length() - \".enabled\".length())\n : property;\n List<String> hits = new ArrayList<>();\n for (Path file : files) {\n String text = Files.readString(file, StandardCharsets.UTF_8);\n if (text.contains(property) || text.contains(prefix)) {\n hits.add(file.getFileName().toString());\n }\n }\n return hits;\n }\n\n public static void main(String[] args) throws IOException {\n List<Path> files = mainSources();\n System.out.println();\n System.out.printf(\"[열거형 상수 %d 개 · main 소스 %,d 개를 훑는다]%n\",\n WebAdvancedFeature.values().length, files.size());\n System.out.println();\n System.out.printf(\" %-26s %-46s %s%n\", \"상수\", \"발행하는 속성 이름\", \"그 이름을 쓰는 main 파일\");\n int unreachable = 0;\n for (WebAdvancedFeature feature : WebAdvancedFeature.values()) {\n List<String> hits = readers(files, feature.propertyName());\n if (hits.isEmpty()) {\n unreachable++;\n }\n System.out.printf(\" %-26s %-46s %s%n\", feature.name(), feature.propertyName(),\n hits.isEmpty() ? \"없음\" : String.join(\" \", hits));\n }\n System.out.println();\n System.out.printf(\" 켤 수 없는 상수 : %d / %d%n\", unreachable, WebAdvancedFeature.values().length);\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$SIB$cp\" -d \"$D/out\" \"$D/AdvancedSwitchProbe.java\" 2>&1 | head -6\njava -Dstdout.encoding=UTF-8 -cp \"$D/out:$SIB$cp\" AdvancedSwitchProbe\n",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template/src",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-03T03:58:30+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a14-f014-advanced-switches.txt",
|
|
"svg": "evidence/rendered/a14-f014-advanced-switches.svg",
|
|
"rawSha256": "e6e3f634f3e226b4ec9c1376b9cf2b15218c6c0e90ba0bf30ba733abd46599eb",
|
|
"lines": 19,
|
|
"redaction": "none — 속성 이름은 열거형이 계산한 값이다"
|
|
}
|