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>
142 lines
12 KiB
XML
142 lines
12 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1338" viewBox="0 0 1200 1338" 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="1336" 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/adapter/inbound/graphql
|
|
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
|
java -version 2>&1 | grep version
|
|
cp=""
|
|
</tspan><tspan x="24" dy="22">while IFS= read -r line; do
|
|
coord=${line%%=*}; confs=${line#*=}
|
|
case ",$confs," in *",testRuntimeClasspath,"*) ;; *)</tspan><tspan x="24" dy="22"> continue ;; esac
|
|
g=${coord%%:*}; rest=${coord#*:}; n=${rest%%:*}; v=${rest##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name </tspan><tspan x="24" dy="22">'*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1) || true
|
|
[ -n "$jar" ] && cp="$cp:$jar"
|
|
done < <</tspan><tspan x="24" dy="22">(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' "$M/gradle.lockfile")
|
|
SIB=$(find "$C/src" -path '*/build/libs/*+21234e38cdb9*.jar' ! </tspan><tspan x="24" dy="22">-name '*-testkit.jar' | sort | tr '\n' ':')
|
|
|
|
cat > "$D/OneOfProbe.java" <<'JAVA'
|
|
import dev.caskeleton.adapter.inbound.g</tspan><tspan x="24" dy="22">raphql.schema.GraphQlOneOfSchemaGate;
|
|
import graphql.ExecutionInput;
|
|
import graphql.GraphQL;
|
|
import graphql.schema.idl.R</tspan><tspan x="24" dy="22">untimeWiring;
|
|
import graphql.schema.idl.SchemaGenerator;
|
|
import graphql.schema.idl.SchemaParser;
|
|
import java.util.Map;
|
|
|
|
</tspan><tspan x="24" dy="22">/** graphql-java 가 스스로 하는 것과 스키마 게이트가 따로 하는 것을 갈라 본다. */
|
|
public final class OneOfProbe {
|
|
|
|
/**</tspan><tspan x="24" dy="22"> 규칙에 맞게 선언한 one-of 입력. 구성원이 전부 널 허용이고 기본값이 없다. */
|
|
private static final String VALID =
|
|
</tspan><tspan x="24" dy="22"> """
|
|
type Query { order(by: OrderSelector!): String }
|
|
input OrderSelector @oneOf { id: ID, number: String }
|
|
</tspan><tspan x="24" dy="22"> """;
|
|
|
|
/** 규칙을 어긴 선언. 구성원 하나가 비널이고 하나는 기본값을 갖는다. */
|
|
private static final String INVA</tspan><tspan x="24" dy="22">LID =
|
|
"""
|
|
type Query { order(by: OrderSelector!): String }
|
|
input OrderSelector @oneOf { id: ID!, numbe</tspan><tspan x="24" dy="22">r: String = "N-1" }
|
|
""";
|
|
|
|
private static GraphQL build(String sdl) {
|
|
var registry = new SchemaParser().parse</tspan><tspan x="24" dy="22">(sdl);
|
|
var wiring =
|
|
RuntimeWiring.newRuntimeWiring()
|
|
.type("Query", builder -> builder.dataFetch</tspan><tspan x="24" dy="22">er("order", env -> "ok"))
|
|
.build();
|
|
return GraphQL.newGraphQL(new SchemaGenerator().makeExecutableSchema</tspan><tspan x="24" dy="22">(registry, wiring)).build();
|
|
}
|
|
|
|
private static void gate(String label, String sdl) {
|
|
System.out.printf(" %-22s </tspan><tspan x="24" dy="22">", label);
|
|
try {
|
|
GraphQlOneOfSchemaGate.verify(sdl);
|
|
System.out.println("통과");
|
|
} catch (RuntimeExc</tspan><tspan x="24" dy="22">eption refused) {
|
|
System.out.println(refused.getClass().getSimpleName() + " : " + refused.getMessage());
|
|
}
|
|
}</tspan><tspan x="24" dy="22">
|
|
|
|
private static void library(String label, String sdl) {
|
|
System.out.printf(" %-22s ", label);
|
|
try {
|
|
bu</tspan><tspan x="24" dy="22">ild(sdl);
|
|
System.out.println("스키마가 만들어짐");
|
|
} catch (RuntimeException refused) {
|
|
System.out.print</tspan><tspan x="24" dy="22">ln(refused.getClass().getSimpleName() + " : " + refused.getMessage());
|
|
}
|
|
}
|
|
|
|
private static void execute(String l</tspan><tspan x="24" dy="22">abel, Object selector) {
|
|
var result =
|
|
build(VALID)
|
|
.execute(
|
|
ExecutionInput.newE</tspan><tspan x="24" dy="22">xecutionInput()
|
|
.query("query Q($s: OrderSelector!) { order(by: $s) }")
|
|
.variabl</tspan><tspan x="24" dy="22">es(Map.of("s", selector))
|
|
.build());
|
|
System.out.printf(" %-30s ", label);
|
|
if (result.getErr</tspan><tspan x="24" dy="22">ors().isEmpty()) {
|
|
System.out.println("통과 · " + result.getData().toString());
|
|
} else {
|
|
System.out.pri</tspan><tspan x="24" dy="22">ntln("거부 · " + result.getErrors().get(0).getMessage());
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
Sys</tspan><tspan x="24" dy="22">tem.out.println();
|
|
System.out.println("[라이브러리가 선언을 어떻게 받는가 · graphql-java 25.0]");
|
|
library("규칙</tspan><tspan x="24" dy="22">에 맞는 선언", VALID);
|
|
library("규칙을 어긴 선언", INVALID);
|
|
|
|
System.out.println();
|
|
System.out.println("[같</tspan><tspan x="24" dy="22">은 두 선언을 스키마 게이트에 넣으면]");
|
|
gate("규칙에 맞는 선언", VALID);
|
|
gate("규칙을 어긴 선언", INVALID);
|
|
|
|
</tspan><tspan x="24" dy="22"> System.out.println();
|
|
System.out.println("[규칙에 맞는 스키마에 값을 실어 보낸다]");
|
|
execute("구성원 하나만 채</tspan><tspan x="24" dy="22">움", Map.of("id", "o-1"));
|
|
execute("구성원 둘을 채움", Map.of("id", "o-1", "number", "N-1"));
|
|
execute("아무것도 </tspan><tspan x="24" dy="22">채우지 않음", Map.of());
|
|
}
|
|
}
|
|
JAVA
|
|
javac -encoding UTF-8 -cp "$SIB$cp" -d "$D/out" "$D/OneOfProbe.java" 2>&1 | head -8
|
|
</tspan><tspan x="24" dy="22">printf '%s\n' '<configuration><root level="OFF"/></configuration>' > "$D/out/logback.xml"
|
|
java -Dstdout.encoding=UTF-8 -</tspan><tspan x="24" dy="22">cp "$D/out:$SIB$cp" OneOfProbe 2>&1 | grep -vE '^[0-9]{4}-|^\s+at |WARN|INFO'
|
|
</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-03T08:09:09+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">[라이브러리가 선언을 어떻게 받는가 · graphql-java 25.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"> 규칙에 맞는 선언 스키마가 만들어짐</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"> 규칙을 어긴 선언 InvalidSchemaException : invalid schema:</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">OneOf input field OrderSelector.id must be nullable.</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">OneOf input field OrderSelector.number cannot have a default value.</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"></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"> 규칙에 맞는 선언 통과</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"> 규칙을 어긴 선언 GraphQlOneOfViolationException : invalid @oneOf input declaration: [OrderSelector.id must be nul</tspan><tspan x="24" dy="22">lable in a @oneOf input, OrderSelector.number must not declare a default value in a @oneOf input]</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"> 구성원 하나만 채움 통과 · {order=ok}</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"> 구성원 둘을 채움 거부 · Exactly one key must be specified for OneOf type 'OrderSelector'.</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"> 아무것도 채우지 않음 거부 · Exactly one key must be specified for OneOf type 'OrderSelector'.</tspan></text>
|
|
</svg>
|