Files
document-haness/docs/clean-architecture-backend-template/final/evidence/rendered/a06-f016-flamingock-probe.svg
T
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

196 lines
12 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1624" viewBox="0 0 1200 1624" 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="1622" 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 &#x27;rm -rf &quot;$D&quot;&#x27; EXIT
cat &gt; &quot;$D/FlamingockFenceProbe.java&quot; &lt;&lt;&#x27;JAVA&#x27;
import dev.caskeleton.ada</tspan><tspan x="24" dy="22">pter.outbound.mongo.security.admin.MongoAdminAuthorization;
import dev.caskeleton.adapter.outbound.mongo.security.admin.</tspan><tspan x="24" dy="22">MongoAdminGateway;
import dev.caskeleton.adapter.outbound.mongo.security.admin.MongoAdminOperation;
import dev.caskeleto</tspan><tspan x="24" dy="22">n.adapter.outbound.mongo.security.admin.MongoAdminRuntimeGuard;
import dev.caskeleton.adapter.outbound.mongo.migration.M</tspan><tspan x="24" dy="22">ongoMigration;
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationChecksum;
import dev.caskeleton.adap</tspan><tspan x="24" dy="22">ter.outbound.mongo.migration.MongoMigrationContext;
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigratio</tspan><tspan x="24" dy="22">nId;
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationLedger;
import dev.caskeleton.adapter.outbound</tspan><tspan x="24" dy="22">.mongo.migration.MongoMigrationLock;
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationPostcondition;</tspan><tspan x="24" dy="22">
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationPrecondition;
import dev.caskeleton.adapter.outbou</tspan><tspan x="24" dy="22">nd.mongo.migration.MongoMigrationResult;
import dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationRunner;
im</tspan><tspan x="24" dy="22">port dev.caskeleton.adapter.outbound.mongo.migration.flamingock.FlamingockLockAdapter;
import java.time.Clock;
import ja</tspan><tspan x="24" dy="22">va.time.Duration;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Optiona</tspan><tspan x="24" dy="22">l;
import java.util.Set;
public final class FlamingockFenceProbe {
private static final MongoMigrationId ID = new Mo</tspan><tspan x="24" dy="22">ngoMigrationId(&quot;20260829-001&quot;);
public static void main(String[] args) {
FlamingockLockAdapter flamingock =
</tspan><tspan x="24" dy="22"> new FlamingockLockAdapter(() -&gt; true, extension -&gt; {}, () -&gt; {});
System.out.println(&quot;FlamingockLockAdapter.held(</tspan><tspan x="24" dy="22">) = &quot; + flamingock.held());
System.out.println(&quot;FlamingockLockAdapter.fence() = &quot; + flamingock.fence()
+ &quot; </tspan><tspan x="24" dy="22"> (UNFENCED = &quot; + MongoMigrationLock.UNFENCED + &quot;)&quot;);
System.out.println();
MongoMigrationRunner runner = new M</tspan><tspan x="24" dy="22">ongoMigrationRunner(new EmptyLedger());
System.out.println(&quot;[검사가 마이그레이션을 보기 전에 걸린다]&quot;);
apply(&quot;</tspan><tspan x="24" dy="22">마이그레이션 0건&quot;, runner, List.of(), flamingock);
apply(&quot;재개 불가 1건&quot;, runner, List.of(nonResumable()), flamingoc</tspan><tspan x="24" dy="22">k);
System.out.println();
System.out.println(&quot;[같은 마이그레이션, 펜스가 있는 리스]&quot;);
apply(&quot;재개 불가 1건</tspan><tspan x="24" dy="22">&quot;, runner, List.of(nonResumable()), new FencedLock(7L));
System.exit(0);
}
private static void apply(
Str</tspan><tspan x="24" dy="22">ing what, MongoMigrationRunner runner, List&lt;MongoMigration&gt; migrations,
MongoMigrationLock lock) {
try {
</tspan><tspan x="24" dy="22"> List&lt;MongoMigrationResult&gt; results = runner.apply(migrations, lock, template());
System.out.println(&quot; &quot; + what +</tspan><tspan x="24" dy="22"> &quot; -&gt; 실행, 결과 &quot; + results.size() + &quot;&quot;
+ (results.isEmpty() ? &quot;&quot; : &quot; &quot; + results.get(0).status()));
} </tspan><tspan x="24" dy="22">catch (RuntimeException refused) {
System.out.println(&quot; &quot; + what + &quot; -&gt; 거절, &quot; + refused.getClass().getSimpleNam</tspan><tspan x="24" dy="22">e());
System.out.println(&quot; &quot; + refused.getMessage().split(&quot; \\[&quot;)[0]);
}
}
/** Completed in one call</tspan><tspan x="24" dy="22">, so it never produces a checkpoint: not a resumable migration. */
private static MongoMigration nonResumable() {
</tspan><tspan x="24" dy="22">return new MongoMigration() {
@Override
public MongoMigrationId id() {
return ID;
}
@Ov</tspan><tspan x="24" dy="22">erride
public MongoMigrationChecksum checksum() {
return new MongoMigrationChecksum(&quot;abc&quot;);
}
</tspan><tspan x="24" dy="22"> @Override
public MongoMigrationPrecondition precondition() {
return context -&gt; true;
}
@Over</tspan><tspan x="24" dy="22">ride
public MongoMigrationResult execute(MongoMigrationContext context) {
return MongoMigrationResult.comp</tspan><tspan x="24" dy="22">leted(1L);
}
@Override
public MongoMigrationPostcondition postcondition() {
return (context, </tspan><tspan x="24" dy="22">result) -&gt; true;
}
};
}
private static MongoMigrationContext template() {
MongoAdminGateway gateway =</tspan><tspan x="24" dy="22">
new MongoAdminGateway(
new MongoAdminRuntimeGuard(true, &quot;app-credential&quot;, &quot;migration-credential&quot;),
</tspan><tspan x="24" dy="22"> MongoAdminAuthorization.routine(Set.of(MongoAdminOperation.APPLY_MIGRATION)),
record -&gt; {},
</tspan><tspan x="24" dy="22"> Clock.fixed(Instant.EPOCH, ZoneOffset.UTC));
return MongoMigrationContext.unleased(
ID, gateway, nul</tspan><tspan x="24" dy="22">l, 100, Duration.ZERO, Duration.ofMinutes(1), false, &quot;release-engineer&quot;);
}
private static final class FencedLock i</tspan><tspan x="24" dy="22">mplements MongoMigrationLock {
private final long fence;
private FencedLock(long fence) {
this.fence = f</tspan><tspan x="24" dy="22">ence;
}
@Override
public boolean held() {
return true;
}
@Override
public long fence() {</tspan><tspan x="24" dy="22">
return fence;
}
@Override
public void refresh(Duration extension) {}
@Override
public void </tspan><tspan x="24" dy="22">close() {}
}
private static final class EmptyLedger implements MongoMigrationLedger {
@Override
public Opt</tspan><tspan x="24" dy="22">ional&lt;AppliedMigration&gt; find(MongoMigrationId migrationId) {
return Optional.empty();
}
@Override
pub</tspan><tspan x="24" dy="22">lic void recordApplied(
MongoMigrationId migrationId, MongoMigrationChecksum checksum, String operator,
</tspan><tspan x="24" dy="22">Instant appliedAt, long fence) {}
@Override
public Optional&lt;dev.caskeleton.adapter.outbound.mongo.migration.Mon</tspan><tspan x="24" dy="22">goMigrationCheckpoint&gt;
findCheckpoint(MongoMigrationId migrationId) {
return Optional.empty();
}
</tspan><tspan x="24" dy="22">@Override
public void saveCheckpoint(
dev.caskeleton.adapter.outbound.mongo.migration.MongoMigrationCheckpoi</tspan><tspan x="24" dy="22">nt checkpoint,
long fence) {}
}
}
JAVA
CACHE=/root/.gradle/caches/modules-2/files-2.1
LOCK=src/adapter/outbou</tspan><tspan x="24" dy="22">nd/persistence-mongo/gradle.lockfile
CP=src/adapter/outbound/persistence-mongo/build/classes/java/main
while IFS= read -</tspan><tspan x="24" dy="22">r line; do
coord=${line%%=*}; confs=${line#*=}
case &quot;,$confs,&quot; in *&quot;,testRuntimeClasspath,&quot;*) ;; *) continue ;; esac</tspan><tspan x="24" dy="22">
g=${coord%%:*}; r=${coord#*:}; n=${r%%:*}; v=${r##*:}
jar=$(find &quot;$CACHE/$g/$n/$v&quot; -name &#x27;*.jar&#x27; ! -name &#x27;*sources*</tspan><tspan x="24" dy="22">&#x27; ! -name &#x27;*javadoc*&#x27; 2&gt;/dev/null | head -1)
[ -n &quot;$jar&quot; ] &amp;&amp; CP=&quot;$CP:$jar&quot;
done &lt; &lt;(grep -E &#x27;^[a-zA-Z0-9._-]+:[^=]+=&#x27;</tspan><tspan x="24" dy="22"> &quot;$LOCK&quot;)
javac -encoding UTF-8 -cp &quot;$CP&quot; -d &quot;$D&quot; &quot;$D/FlamingockFenceProbe.java&quot;
java -Dstdout.encoding=UTF-8 -cp &quot;$D:$</tspan><tspan x="24" dy="22">CP&quot; FlamingockFenceProbe 2&gt;&amp;1 \
| grep -vE &#x27;^[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3} &#x27; </tspan></text>
<text x="24" y="1234" 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="1256" 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-02T07:44:36+00:00 · exit 0</tspan></text>
<line x1="24" y1="1270" x2="1176" y2="1270" stroke="#30363d"/>
<text x="24" y="1310" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">FlamingockLockAdapter.held() = true</tspan></text>
<text x="24" y="1332" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">FlamingockLockAdapter.fence() = -1 (UNFENCED = -1)</tspan></text>
<text x="24" y="1354" 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="1376" 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="1398" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 마이그레이션 0건 -&gt; 거절, MongoOperationRejectedException</tspan></text>
<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"> this migration lease exposes no fencing token, so a stalled runner cannot be excluded; use a fenced lease or run the migra</tspan><tspan x="24" dy="22">tion inside a maintenance window with the application stopped</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"> 재개 불가 1건 -&gt; 거절, MongoOperationRejectedException</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"> this migration lease exposes no fencing token, so a stalled runner cannot be excluded; use a fenced lease or run the migra</tspan><tspan x="24" dy="22">tion inside a maintenance window with the application stopped</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"></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"> 재개 불가 1건 -&gt; 실행, 결과 1건 COMPLETED</tspan></text>
</svg>