Files
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
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>
2026-09-04 22:51:59 +09:00

15 lines
6.9 KiB
JSON

{
"assetKey": "a06-f013-recordapplied-probe",
"kind": "terminal",
"command": "set -e\nD=$(mktemp -d)\ntrap 'docker rm -f mg-rs >/dev/null 2>&1; rm -rf \"$D\"' EXIT\n\ndocker rm -f mg-rs >/dev/null 2>&1 || true\ndocker run -d --name mg-rs --network container:workmachine \\\n mongo:8.0.16 --port 57020 --bind_ip 127.0.0.1 --replSet rs0 >/dev/null\nfor i in $(seq 1 60); do\n docker exec mg-rs mongosh --port 57020 --quiet --eval 'db.runCommand({ping:1}).ok' 2>/dev/null \\\n | grep -q 1 && break\n sleep 1\ndone\ndocker exec mg-rs mongosh --port 57020 --quiet \\\n --eval 'rs.initiate({_id:\"rs0\",members:[{_id:0,host:\"127.0.0.1:57020\"}]})' >/dev/null 2>&1\nfor i in $(seq 1 60); do\n docker exec mg-rs mongosh --port 57020 --quiet --eval 'db.hello().isWritablePrimary' 2>/dev/null \\\n | grep -q true && break\n sleep 1\ndone\n\ncat > \"$D/LedgerFenceProbe.java\" <<'JAVA'\nimport com.mongodb.MongoWriteException;\nimport com.mongodb.client.MongoClients;\nimport com.mongodb.client.MongoDatabase;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoCollectionMigrationLedger;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationCheckpoint;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationChecksum;\nimport dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationId;\nimport java.time.Instant;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\nimport org.bson.Document;\n\npublic final class LedgerFenceProbe {\n\n private static final MongoMigrationId ID = new MongoMigrationId(\"20260829-001\");\n private static final Instant NOW = Instant.parse(\"2026-09-02T00:00:00Z\");\n private static final long LIVE = 5L;\n private static final long STALE = 1L;\n\n public static void main(String[] args) {\n MongoDatabase indexed = MongoClients.create(args[0]).getDatabase(\"indexed\");\n indexed.drop();\n MongoCollectionMigrationLedger ledger = new MongoCollectionMigrationLedger(indexed);\n ledger.ensureIndexes();\n\n System.out.println(\"[준비] 살아 있는 실행자가 펜스 \" + LIVE + \" 로 체크포인트를 쓴다\");\n ledger.saveCheckpoint(new MongoMigrationCheckpoint(ID, \"o-900\", 900L, NOW), LIVE);\n System.out.println(\" 체크포인트 : \" + one(indexed, \"mongoMigrationCheckpoint\"));\n System.out.println();\n\n System.out.println(\"[밀려난 실행자, 펜스 \" + STALE + \"] 같은 계약을 두 메서드에 건다\");\n attempt(\"saveCheckpoint\", () -> ledger.saveCheckpoint(\n new MongoMigrationCheckpoint(ID, \"o-1\", 1L, NOW), STALE));\n attempt(\"recordApplied \", () -> ledger.recordApplied(\n ID, new MongoMigrationChecksum(\"superseded\"), \"stale-runner\", NOW, STALE));\n System.out.println(\" 원장 항목 : \" + one(indexed, \"mongoMigrationLedger\"));\n System.out.println();\n\n System.out.println(\"[살아 있는 실행자, 펜스 \" + LIVE + \"] 밀려난 쪽이 쓴 뒤에 기록한다\");\n attempt(\"recordApplied \", () -> ledger.recordApplied(\n ID, new MongoMigrationChecksum(\"live\"), \"live-runner\", NOW, LIVE));\n System.out.println(\" 원장 항목 : \" + one(indexed, \"mongoMigrationLedger\"));\n System.out.println();\n\n System.out.println(\"[대조 1] 펜스가 미지정 값이면 두 메서드 다 거절한다\");\n attempt(\"saveCheckpoint\", () -> ledger.saveCheckpoint(\n new MongoMigrationCheckpoint(ID, \"o-0\", 0L, NOW), -1L));\n attempt(\"recordApplied \", () -> ledger.recordApplied(\n ID, new MongoMigrationChecksum(\"unfenced\"), \"runner\", NOW, -1L));\n System.out.println();\n\n MongoDatabase bare = MongoClients.create(args[0]).getDatabase(\"bare\");\n bare.drop();\n MongoCollectionMigrationLedger unindexed = new MongoCollectionMigrationLedger(bare);\n System.out.println(\"[대조 2] ensureIndexes 를 부르지 않은 원장에서 같은 경합\");\n attempt(\"recordApplied \", () -> unindexed.recordApplied(\n ID, new MongoMigrationChecksum(\"superseded\"), \"stale-runner\", NOW, STALE));\n attempt(\"recordApplied \", () -> unindexed.recordApplied(\n ID, new MongoMigrationChecksum(\"live\"), \"live-runner\", NOW, LIVE));\n System.out.println(\" 원장 항목 수 : \" + bare.getCollection(\"mongoMigrationLedger\").countDocuments());\n for (Document row : bare.getCollection(\"mongoMigrationLedger\").find()) {\n row.remove(\"_id\");\n System.out.println(\" \" + row.toJson());\n }\n System.exit(0);\n }\n\n private static String one(MongoDatabase database, String collection) {\n Document found = database.getCollection(collection).find().first();\n if (found == null) {\n return \"(없음)\";\n }\n found.remove(\"_id\");\n return found.toJson();\n }\n\n private static void attempt(String what, Runnable call) {\n try {\n call.run();\n System.out.println(\" \" + what + \" -> 수용\");\n } catch (RuntimeException rejected) {\n System.out.println(\" \" + what + \" -> 거절, \" + describe(rejected));\n }\n }\n\n private static String describe(RuntimeException failure) {\n String name = failure.getClass().getSimpleName();\n if (failure instanceof MongoWriteException write) {\n Matcher index = Pattern.compile(\"index: (\\\\S+)\").matcher(write.getError().getMessage());\n return name + \" code=\" + write.getError().getCode()\n + (index.find() ? \" index=\" + index.group(1) : \"\");\n }\n String line = failure.getMessage() == null ? \"\" : failure.getMessage().split(\"\\n\")[0].split(\" \\\\[\")[0];\n return name + \": \" + (line.length() > 110 ? line.substring(0, 110) + \" …\" : line);\n }\n}\nJAVA\n\nCACHE=/root/.gradle/caches/modules-2/files-2.1\nLOCK=src/adapter/outbound/persistence-mongo/gradle.lockfile\nCP=src/adapter/outbound/persistence-mongo/build/classes/java/main\nwhile IFS= read -r line; do\n coord=${line%%=*}; confs=${line#*=}\n case \",$confs,\" in *\",testRuntimeClasspath,\"*) ;; *) continue ;; esac\n g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}\n jar=$(find \"$CACHE/$g/$n/$v\" -name '*.jar' ! -name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)\n [ -n \"$jar\" ] && CP=\"$CP:$jar\"\ndone < <(grep -E '^[a-zA-Z0-9._-]+:[^=]+=' \"$LOCK\")\n\njavac -encoding UTF-8 -cp \"$CP\" -d \"$D\" \"$D/LedgerFenceProbe.java\"\njava -Dstdout.encoding=UTF-8 -cp \"$D:$CP\" LedgerFenceProbe \\\n 'mongodb://127.0.0.1:57020/probe?replicaSet=rs0' 2>&1 \\\n | grep -vE '^[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} ' ",
"cwd": "/shared/codebase/clean-architecture-backend-template",
"exitCode": 0,
"executedAt": "2026-09-02T07:39:34+00:00",
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
"raw": "evidence/raw/a06-f013-recordapplied-probe.txt",
"svg": "evidence/rendered/a06-f013-recordapplied-probe.svg",
"rawSha256": "60c3d725acf6daf97a6c4741f1e87034953c637f63fcb4359b303926dd86438b",
"lines": 22,
"redaction": "none — 임시 컨테이너 실행 출력, 자격증명 없음"
}