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
7.5 KiB
JSON
15 lines
7.5 KiB
JSON
{
|
|
"assetKey": "a06-f018-changestreams-false-wiring",
|
|
"kind": "terminal",
|
|
"command": "set -e\nD=$(mktemp -d)\ntrap 'rm -rf \"$D\"' EXIT\n\nprintf %s '<configuration><root level=\"OFF\"/></configuration>' > \"$D/logback-off.xml\"\n\ncat > \"$D/ChangeStreamWiringProbe.java\" <<'JAVA'\nimport static org.mockito.Mockito.mock;\n\nimport com.mongodb.client.MongoClients;\nimport dev.caskeleton.adapter.outbound.mongo.api.consistency.MongoConsistencyRegistry;\nimport dev.caskeleton.adapter.outbound.mongo.api.observation.MongoOperationObserver;\nimport dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformAutoConfiguration;\nimport dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatformSettings;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamSubscription;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeCheckpointStore;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.MongoResumeTokenCodec;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.consumer.MongoChangeStreamSource;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.consumer.ReactiveMongoChangeStreamConsumer;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeDeduplicationStore;\nimport dev.caskeleton.adapter.outbound.mongo.changestream.projector.MongoChangeProjector;\nimport dev.caskeleton.adapter.outbound.mongo.failure.MongoFailureTranslator;\nimport dev.caskeleton.adapter.outbound.mongo.imperative.MongoCollectionProfileRegistry;\nimport org.springframework.boot.autoconfigure.AutoConfigurations;\nimport org.springframework.boot.test.context.assertj.AssertableApplicationContext;\nimport org.springframework.boot.test.context.runner.ApplicationContextRunner;\nimport org.springframework.data.mongodb.core.MongoTemplate;\nimport org.springframework.data.mongodb.core.ReactiveMongoTemplate;\nimport org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;\nimport org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;\n\npublic final class ChangeStreamWiringProbe {\n\n private static final String ENABLED = \"ca-skeleton.persistence-mongo.enabled=true\";\n private static final String FLAG = \"ca-skeleton.persistence-mongo.platform.change-streams=true\";\n\n /** The whole auto-configuration, not the nested class: the flag lives on the outer one. */\n private static final ApplicationContextRunner BASE =\n new ApplicationContextRunner()\n .withConfiguration(AutoConfigurations.of(MongoPlatformAutoConfiguration.class))\n .withBean(MongoTemplate.class, ChangeStreamWiringProbe::blocking)\n .withBean(MongoConsistencyRegistry.class, MongoConsistencyRegistry::standard)\n .withBean(\n MongoCollectionProfileRegistry.class,\n () -> MongoCollectionProfileRegistry.builder().register(\"events\", \"events\").build())\n .withBean(MongoFailureTranslator.class, () -> mock(MongoFailureTranslator.class))\n .withBean(MongoOperationObserver.class, () -> mock(MongoOperationObserver.class));\n\n public static void main(String[] args) {\n System.out.println(\"[모듈 opt-in 없이]\");\n report(\"기본 \", BASE.withBean(ReactiveMongoTemplate.class,\n ChangeStreamWiringProbe::reactive));\n System.out.println();\n\n ApplicationContextRunner on = BASE.withPropertyValues(ENABLED);\n System.out.println(\"[opt-in, 리액티브 템플릿만]\");\n report(\"기본 \", on.withBean(ReactiveMongoTemplate.class,\n ChangeStreamWiringProbe::reactive));\n report(\"change-streams=true \", on.withBean(ReactiveMongoTemplate.class,\n ChangeStreamWiringProbe::reactive).withPropertyValues(FLAG));\n System.out.println();\n\n System.out.println(\"[opt-in, 배포가 공급해야 하는 다섯을 함께]\");\n report(\"기본 \", withFive(on.withBean(ReactiveMongoTemplate.class,\n ChangeStreamWiringProbe::reactive)));\n report(\"change-streams=true \", withFive(on.withBean(ReactiveMongoTemplate.class,\n ChangeStreamWiringProbe::reactive)).withPropertyValues(FLAG));\n System.out.println();\n\n System.out.println(\"[opt-in, 리액티브 템플릿 없이]\");\n report(\"기본 \", on);\n System.exit(0);\n }\n\n private static ApplicationContextRunner withFive(ApplicationContextRunner runner) {\n return runner\n .withBean(MongoChangeStreamSubscription.class,\n () -> MongoChangeStreamSubscription.of(\"probe\", \"events\"))\n .withBean(MongoResumeCheckpointStore.class, () -> mock(MongoResumeCheckpointStore.class))\n .withBean(MongoResumeTokenCodec.class, () -> mock(MongoResumeTokenCodec.class))\n .withBean(MongoChangeProjector.class, () -> mock(MongoChangeProjector.class))\n .withBean(MongoChangeDeduplicationStore.class,\n () -> mock(MongoChangeDeduplicationStore.class));\n }\n\n private static void report(String label, ApplicationContextRunner runner) {\n runner.run(context -> System.out.println(\" \" + label\n + \" 드라이버쪽=\" + present(context, MongoChangeStreamSource.class)\n + \" 소비자=\" + present(context, ReactiveMongoChangeStreamConsumer.class)\n + \" 설정빈=\" + present(context, MongoPlatformSettings.class)\n + flag(context)));\n }\n\n private static String flag(AssertableApplicationContext context) {\n if (context.getBeanNamesForType(MongoPlatformSettings.class).length == 0) {\n return \"\";\n }\n return \" changeStreams()=\" + context.getBean(MongoPlatformSettings.class).changeStreams();\n }\n\n private static String present(AssertableApplicationContext context, Class<?> type) {\n return context.getBeanNamesForType(type).length > 0 ? \"있음\" : \"없음\";\n }\n\n private static MongoTemplate blocking() {\n return new MongoTemplate(\n new SimpleMongoClientDatabaseFactory(\n MongoClients.create(\"mongodb://127.0.0.1:27017\"), \"wiring-probe\"));\n }\n\n private static ReactiveMongoTemplate reactive() {\n return new ReactiveMongoTemplate(\n new SimpleReactiveMongoDatabaseFactory(\n com.mongodb.reactivestreams.client.MongoClients.create(\"mongodb://127.0.0.1:27017\"),\n \"wiring-probe\"));\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/ChangeStreamWiringProbe.java\"\njava -XX:+EnableDynamicAgentLoading -Dlogback.configurationFile=\"$D/logback-off.xml\" \\\n -Dstdout.encoding=UTF-8 -cp \"$D:$CP\" ChangeStreamWiringProbe 2>/dev/null",
|
|
"cwd": "/shared/codebase/clean-architecture-backend-template",
|
|
"exitCode": 0,
|
|
"executedAt": "2026-09-02T08:19:48+00:00",
|
|
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
|
|
"raw": "evidence/raw/a06-f018-changestreams-false-wiring.txt",
|
|
"svg": "evidence/rendered/a06-f018-changestreams-false-wiring.svg",
|
|
"rawSha256": "ee8bcb3995b4c5019e707e9b9a08ae524712622b7afdac3cf84102f82e39781d",
|
|
"lines": 13,
|
|
"redaction": "none — 밀폐 탐침 실행 출력, 자격증명 없음"
|
|
}
|