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>
161 lines
14 KiB
XML
161 lines
14 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1734" viewBox="0 0 1200 1734" 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="1732" 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
|
|
D=$(mktemp -d)
|
|
trap 'rm -rf "$D"' EXIT
|
|
|
|
printf %s '<configuration><root level="OFF"/></configuration>' > "$D/lo</tspan><tspan x="24" dy="22">gback-off.xml"
|
|
|
|
cat > "$D/ChangeStreamWiringProbe.java" <<'JAVA'
|
|
import static org.mockito.Mockito.mock;
|
|
|
|
import com.mon</tspan><tspan x="24" dy="22">godb.client.MongoClients;
|
|
import dev.caskeleton.adapter.outbound.mongo.api.consistency.MongoConsistencyRegistry;
|
|
import </tspan><tspan x="24" dy="22">dev.caskeleton.adapter.outbound.mongo.api.observation.MongoOperationObserver;
|
|
import dev.caskeleton.adapter.outbound.mon</tspan><tspan x="24" dy="22">go.autoconfigure.MongoPlatformAutoConfiguration;
|
|
import dev.caskeleton.adapter.outbound.mongo.autoconfigure.MongoPlatfor</tspan><tspan x="24" dy="22">mSettings;
|
|
import dev.caskeleton.adapter.outbound.mongo.changestream.MongoChangeStreamSubscription;
|
|
import dev.caskeleto</tspan><tspan x="24" dy="22">n.adapter.outbound.mongo.changestream.MongoResumeCheckpointStore;
|
|
import dev.caskeleton.adapter.outbound.mongo.changestr</tspan><tspan x="24" dy="22">eam.MongoResumeTokenCodec;
|
|
import dev.caskeleton.adapter.outbound.mongo.changestream.consumer.MongoChangeStreamSource;
|
|
i</tspan><tspan x="24" dy="22">mport dev.caskeleton.adapter.outbound.mongo.changestream.consumer.ReactiveMongoChangeStreamConsumer;
|
|
import dev.caskelet</tspan><tspan x="24" dy="22">on.adapter.outbound.mongo.changestream.projector.MongoChangeDeduplicationStore;
|
|
import dev.caskeleton.adapter.outbound.m</tspan><tspan x="24" dy="22">ongo.changestream.projector.MongoChangeProjector;
|
|
import dev.caskeleton.adapter.outbound.mongo.failure.MongoFailureTrans</tspan><tspan x="24" dy="22">lator;
|
|
import dev.caskeleton.adapter.outbound.mongo.imperative.MongoCollectionProfileRegistry;
|
|
import org.springframewor</tspan><tspan x="24" dy="22">k.boot.autoconfigure.AutoConfigurations;
|
|
import org.springframework.boot.test.context.assertj.AssertableApplicationConte</tspan><tspan x="24" dy="22">xt;
|
|
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
|
import org.springframework.data.mongod</tspan><tspan x="24" dy="22">b.core.MongoTemplate;
|
|
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
|
|
import org.springframework.dat</tspan><tspan x="24" dy="22">a.mongodb.core.SimpleMongoClientDatabaseFactory;
|
|
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabas</tspan><tspan x="24" dy="22">eFactory;
|
|
|
|
public final class ChangeStreamWiringProbe {
|
|
|
|
private static final String ENABLED = "ca-skeleton.persistenc</tspan><tspan x="24" dy="22">e-mongo.enabled=true";
|
|
private static final String FLAG = "ca-skeleton.persistence-mongo.platform.change-streams=true"</tspan><tspan x="24" dy="22">;
|
|
|
|
/** The whole auto-configuration, not the nested class: the flag lives on the outer one. */
|
|
private static final </tspan><tspan x="24" dy="22">ApplicationContextRunner BASE =
|
|
new ApplicationContextRunner()
|
|
.withConfiguration(AutoConfigurations.of(</tspan><tspan x="24" dy="22">MongoPlatformAutoConfiguration.class))
|
|
.withBean(MongoTemplate.class, ChangeStreamWiringProbe::blocking)
|
|
</tspan><tspan x="24" dy="22"> .withBean(MongoConsistencyRegistry.class, MongoConsistencyRegistry::standard)
|
|
.withBean(
|
|
Mo</tspan><tspan x="24" dy="22">ngoCollectionProfileRegistry.class,
|
|
() -> MongoCollectionProfileRegistry.builder().register("events", "eve</tspan><tspan x="24" dy="22">nts").build())
|
|
.withBean(MongoFailureTranslator.class, () -> mock(MongoFailureTranslator.class))
|
|
.wi</tspan><tspan x="24" dy="22">thBean(MongoOperationObserver.class, () -> mock(MongoOperationObserver.class));
|
|
|
|
public static void main(String[] args</tspan><tspan x="24" dy="22">) {
|
|
System.out.println("[모듈 opt-in 없이]");
|
|
report("기본 ", BASE.withBean(ReactiveMongoTemplate</tspan><tspan x="24" dy="22">.class,
|
|
ChangeStreamWiringProbe::reactive));
|
|
System.out.println();
|
|
|
|
ApplicationContextRunner on = BASE.w</tspan><tspan x="24" dy="22">ithPropertyValues(ENABLED);
|
|
System.out.println("[opt-in, 리액티브 템플릿만]");
|
|
report("기본 ", on</tspan><tspan x="24" dy="22">.withBean(ReactiveMongoTemplate.class,
|
|
ChangeStreamWiringProbe::reactive));
|
|
report("change-streams=true ", o</tspan><tspan x="24" dy="22">n.withBean(ReactiveMongoTemplate.class,
|
|
ChangeStreamWiringProbe::reactive).withPropertyValues(FLAG));
|
|
System</tspan><tspan x="24" dy="22">.out.println();
|
|
|
|
System.out.println("[opt-in, 배포가 공급해야 하는 다섯을 함께]");
|
|
report("기본 "</tspan><tspan x="24" dy="22">, withFive(on.withBean(ReactiveMongoTemplate.class,
|
|
ChangeStreamWiringProbe::reactive)));
|
|
report("change-str</tspan><tspan x="24" dy="22">eams=true ", withFive(on.withBean(ReactiveMongoTemplate.class,
|
|
ChangeStreamWiringProbe::reactive)).withPropertyV</tspan><tspan x="24" dy="22">alues(FLAG));
|
|
System.out.println();
|
|
|
|
System.out.println("[opt-in, 리액티브 템플릿 없이]");
|
|
report("기본 </tspan><tspan x="24" dy="22"> ", on);
|
|
System.exit(0);
|
|
}
|
|
|
|
private static ApplicationContextRunner withFive(ApplicationContextRunner </tspan><tspan x="24" dy="22">runner) {
|
|
return runner
|
|
.withBean(MongoChangeStreamSubscription.class,
|
|
() -> MongoChangeStreamSu</tspan><tspan x="24" dy="22">bscription.of("probe", "events"))
|
|
.withBean(MongoResumeCheckpointStore.class, () -> mock(MongoResumeCheckpointSt</tspan><tspan x="24" dy="22">ore.class))
|
|
.withBean(MongoResumeTokenCodec.class, () -> mock(MongoResumeTokenCodec.class))
|
|
.withBean(Mo</tspan><tspan x="24" dy="22">ngoChangeProjector.class, () -> mock(MongoChangeProjector.class))
|
|
.withBean(MongoChangeDeduplicationStore.class,</tspan><tspan x="24" dy="22">
|
|
() -> mock(MongoChangeDeduplicationStore.class));
|
|
}
|
|
|
|
private static void report(String label, Applicati</tspan><tspan x="24" dy="22">onContextRunner runner) {
|
|
runner.run(context -> System.out.println(" " + label
|
|
+ " 드라이버쪽=" + present(c</tspan><tspan x="24" dy="22">ontext, MongoChangeStreamSource.class)
|
|
+ " 소비자=" + present(context, ReactiveMongoChangeStreamConsumer.class)</tspan><tspan x="24" dy="22">
|
|
+ " 설정빈=" + present(context, MongoPlatformSettings.class)
|
|
+ flag(context)));
|
|
}
|
|
|
|
private static </tspan><tspan x="24" dy="22">String flag(AssertableApplicationContext context) {
|
|
if (context.getBeanNamesForType(MongoPlatformSettings.class).len</tspan><tspan x="24" dy="22">gth == 0) {
|
|
return "";
|
|
}
|
|
return " changeStreams()=" + context.getBean(MongoPlatformSettings.class).change</tspan><tspan x="24" dy="22">Streams();
|
|
}
|
|
|
|
private static String present(AssertableApplicationContext context, Class<?> type) {
|
|
return contex</tspan><tspan x="24" dy="22">t.getBeanNamesForType(type).length > 0 ? "있음" : "없음";
|
|
}
|
|
|
|
private static MongoTemplate blocking() {
|
|
return ne</tspan><tspan x="24" dy="22">w MongoTemplate(
|
|
new SimpleMongoClientDatabaseFactory(
|
|
MongoClients.create("mongodb://127.0.0.1:2701</tspan><tspan x="24" dy="22">7"), "wiring-probe"));
|
|
}
|
|
|
|
private static ReactiveMongoTemplate reactive() {
|
|
return new ReactiveMongoTemplate(
|
|
</tspan><tspan x="24" dy="22"> new SimpleReactiveMongoDatabaseFactory(
|
|
com.mongodb.reactivestreams.client.MongoClients.create("mongod</tspan><tspan x="24" dy="22">b://127.0.0.1:27017"),
|
|
"wiring-probe"));
|
|
}
|
|
}
|
|
JAVA
|
|
|
|
CACHE=/root/.gradle/caches/modules-2/files-2.1
|
|
LOCK=src</tspan><tspan x="24" dy="22">/adapter/outbound/persistence-mongo/gradle.lockfile
|
|
CP=src/adapter/outbound/persistence-mongo/build/classes/java/main
|
|
wh</tspan><tspan x="24" dy="22">ile IFS= read -r line; do
|
|
coord=${line%%=*}; confs=${line#*=}
|
|
case ",$confs," in *",testRuntimeClasspath,"*) ;; *) c</tspan><tspan x="24" dy="22">ontinue ;; esac
|
|
g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}
|
|
jar=$(find "$CACHE/$g/$n/$v" -name '*.jar' ! -</tspan><tspan x="24" dy="22">name '*sources*' ! -name '*javadoc*' 2>/dev/null | head -1)
|
|
[ -n "$jar" ] && CP="$CP:$jar"
|
|
done < <(grep -E '^[a-zA-Z0</tspan><tspan x="24" dy="22">-9._-]+:[^=]+=' "$LOCK")
|
|
|
|
javac -encoding UTF-8 -cp "$CP" -d "$D" "$D/ChangeStreamWiringProbe.java"
|
|
java -XX:+EnableDyna</tspan><tspan x="24" dy="22">micAgentLoading -Dlogback.configurationFile="$D/logback-off.xml" \
|
|
-Dstdout.encoding=UTF-8 -cp "$D:$CP" ChangeStreamWi</tspan><tspan x="24" dy="22">ringProbe 2>/dev/null</tspan></text>
|
|
<text x="24" y="1344" 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</tspan></text>
|
|
<text x="24" y="1366" 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-02T08:19:48+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="1380" x2="1176" y2="1380" stroke="#30363d"/>
|
|
<text x="24" y="1420" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[모듈 opt-in 없이]</tspan></text>
|
|
<text x="24" y="1442" 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="1464" 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="1486" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[opt-in, 리액티브 템플릿만]</tspan></text>
|
|
<text x="24" y="1508" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=있음 소비자=없음 설정빈=있음 changeStreams()=false</tspan></text>
|
|
<text x="24" y="1530" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> change-streams=true 드라이버쪽=있음 소비자=없음 설정빈=있음 changeStreams()=false</tspan></text>
|
|
<text x="24" y="1552" 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="1574" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[opt-in, 배포가 공급해야 하는 다섯을 함께]</tspan></text>
|
|
<text x="24" y="1596" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=있음 소비자=있음 설정빈=있음 changeStreams()=false</tspan></text>
|
|
<text x="24" y="1618" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> change-streams=true 드라이버쪽=있음 소비자=있음 설정빈=있음 changeStreams()=false</tspan></text>
|
|
<text x="24" y="1640" 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="1662" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">[opt-in, 리액티브 템플릿 없이]</tspan></text>
|
|
<text x="24" y="1684" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 기본 드라이버쪽=없음 소비자=없음 설정빈=있음 changeStreams()=false</tspan></text>
|
|
</svg>
|