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
6.3 KiB
JSON
15 lines
6.3 KiB
JSON
{
|
|
"assetKey": "a11-f004-number-bytes",
|
|
"kind": "terminal",
|
|
"command": "set -e\nD=$(mktemp -d); trap 'rm -rf \"$D\"' EXIT\nH=/shared/codebase/clean-architecture-backend-template/src/adapter/outbound/httpclient\nJAR=$H/build/libs/httpclient-0.0.1+21234e38cdb9.jar\nCACHE=/root/.gradle/caches/modules-2/files-2.1\ncp=\"\"\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",runtimeClasspath,\"*) ;; *) continue ;; esac\n group=${coord%%:*}; rest=${coord#*:}; name=${rest%%:*}; ver=${rest##*:}\n jar=$(find \"$CACHE/$group/$name/$ver\" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)\n [ -n \"$jar\" ] && cp=\"$cp:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$H/gradle.lockfile\")\nCP=\"$JAR${cp}\"\njava -version 2>&1 | head -1\ncat > \"$D/ReplayabilityProbe.java\" <<'JAVA'\nimport dev.caskeleton.adapter.outbound.httpclient.api.body.ObjectBody;\nimport java.io.ByteArrayOutputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicLong;\nimport java.util.concurrent.atomic.DoubleAccumulator;\nimport java.util.concurrent.atomic.DoubleAdder;\nimport java.util.concurrent.atomic.LongAccumulator;\nimport java.util.concurrent.atomic.LongAdder;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpOutputMessage;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.converter.HttpMessageConverter;\nimport org.springframework.http.converter.json.JacksonJsonHttpMessageConverter;\nimport org.springframework.web.client.RestClient;\n\npublic final class ReplayabilityProbe {\n\n /** A DTO of the shape the platform is built around, with a counter in it. */\n public record Order(String id, AtomicInteger attempt) {}\n\n public record Basket(String id, List<String> lines) {}\n\n private static final JacksonJsonHttpMessageConverter JSON =\n new JacksonJsonHttpMessageConverter();\n\n private static String verdict(Object value) {\n return ObjectBody.json(value).replayability().toString();\n }\n\n /** Encodes through the converter a default RestClient uses for a JSON body. */\n private static byte[] encode(Object value) throws Exception {\n ByteArrayOutputStream sink = new ByteArrayOutputStream();\n JSON.write(\n value,\n MediaType.APPLICATION_JSON,\n new HttpOutputMessage() {\n @Override\n public OutputStream getBody() {\n return sink;\n }\n\n @Override\n public HttpHeaders getHeaders() {\n return new HttpHeaders();\n }\n });\n return sink.toByteArray();\n }\n\n public static void main(String[] args) throws Exception {\n System.out.println(\"[요청 본문을 인코딩하는 변환기] RestClient 기본 목록\");\n RestClient.create()\n .mutate()\n .messageConverters(\n converters ->\n converters.stream()\n .filter(c -> c.canWrite(Order.class, MediaType.APPLICATION_JSON))\n .forEach(c -> System.out.println(\" \" + c.getClass().getName())));\n System.out.println();\n\n System.out.println(\"[java.util.concurrent.atomic 의 Number 하위 타입]\");\n Object[] mutables = {\n new AtomicInteger(1), new AtomicLong(1L), new LongAdder(), new DoubleAdder(),\n new LongAccumulator(Long::sum, 0L), new DoubleAccumulator(Double::sum, 0d),\n };\n for (Object mutable : mutables) {\n System.out.printf(\n \" %-20s Number 상속 %-5s 판정 %s%n\",\n mutable.getClass().getSimpleName(), mutable instanceof Number, verdict(mutable));\n }\n System.out.println();\n\n System.out.println(\"[상자 원시형과 원자 계열 사이에 남는 둘]\");\n java.math.BigInteger bigInteger = new java.math.BigInteger(\"7\");\n java.math.BigDecimal bigDecimal = new java.math.BigDecimal(\"7.5\");\n bigInteger.add(java.math.BigInteger.ONE);\n bigDecimal.add(java.math.BigDecimal.ONE);\n System.out.println(\" BigInteger add 뒤 자기 값 \" + bigInteger + \" 판정 \" + verdict(bigInteger));\n System.out.println(\" BigDecimal add 뒤 자기 값 \" + bigDecimal + \" 판정 \" + verdict(bigDecimal));\n AtomicInteger counter = new AtomicInteger(7);\n counter.incrementAndGet();\n System.out.println(\" AtomicInteger increment 뒤 자기 값 \" + counter\n + \" 판정 \" + verdict(counter));\n System.out.println();\n\n System.out.println(\"[성분에 카운터를 둔 record]\");\n Order order = new Order(\"A-1\", new AtomicInteger(1));\n ObjectBody body = ObjectBody.json(order);\n byte[] first = encode(body.value());\n System.out.println(\" 판정 : \" + body.replayability());\n System.out.println(\" 첫 인코딩 : \" + new String(first));\n order.attempt().incrementAndGet();\n byte[] second = encode(body.value());\n System.out.println(\" 다시 인코딩 : \" + new String(second));\n System.out.println(\" 판정(그대로) : \" + body.replayability());\n System.out.println(\" 같은 바이트인가 : \" + java.util.Arrays.equals(first, second));\n System.out.println();\n\n System.out.println(\"[검사가 잡는 쪽]\");\n List<String> held = new ArrayList<>(List.of(\"a\"));\n System.out.println(\" ArrayList 자체 판정 \" + verdict(held));\n System.out.println(\" ArrayList 를 감싼 record 판정 \" + verdict(new Basket(\"B-1\", held)));\n System.out.println(\" List.of 를 감싼 record 판정 \" + verdict(new Basket(\"B-2\", List.of(\"a\"))));\n }\n}\nJAVA\njavac -encoding UTF-8 -cp \"$CP\" -d \"$D\" \"$D/ReplayabilityProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$CP:$D\" ReplayabilityProbe\n",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template/src/adapter/outbound/httpclient",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-02T13:29:59+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a11-f004-number-bytes.txt",
|
|
"svg": "evidence/rendered/a11-f004-number-bytes.svg",
|
|
"rawSha256": "186280627ec9e702e793df81b3246c784304a524a50a8d5b4af6651068a82ddb",
|
|
"lines": 35,
|
|
"redaction": "none — 밀폐 탐침 실행 출력, 자격증명 없음"
|
|
}
|