Files
document-haness/docs/clean-architecture-backend-template/final/evidence/rendered/a-startup-validator-that-requires-a-key-nothing-signs-with.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

101 lines
16 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1690" viewBox="0 0 1200 1690" 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="1688" 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">$ G=adapter/inbound/graphql/src; M=$G/main/java/dev/caskeleton/adapter/inbound/graphql; T=$G/test/java/dev/caskeleton/ad</tspan><tspan x="24" dy="22">apter/inbound/graphql
echo &#x27;# 시작 검증기가 거절하는 문구&#x27;
sed -n &#x27;43p&#x27; $M/autoconfigure/GraphQlPlatformStartupValidator</tspan><tspan x="24" dy="22">.java
echo -n &#x27;# 그 검증기는 실제로 돈다 — 자동설정의 호출 지점: &#x27;
grep -n &#x27;validator.validate(properties)&#x27; $M/autoco</tspan><tspan x="24" dy="22">nfigure/GraphQlPlatformAutoConfiguration.java
echo
echo &#x27;# 그 키 설정을 소비하는 main 코드&#x27;
grep -rn &#x27;cursor().keyIds()&#x27;</tspan><tspan x="24" dy="22"> --include=*.java $M | sed &#x27;s|.*/||&#x27;
echo -n &#x27;# 그중 액추에이터 엔드포인트를 만드는 main 코드: &#x27;
grep -rn &#x27;new GraphQl</tspan><tspan x="24" dy="22">PlatformActuatorEndpoint&#x27; --include=*.java . | grep -c &#x27;/main/&#x27;
echo
echo &#x27;# 서명 코덱과 키링&#x27;
echo -n &#x27; GraphQlCursorC</tspan><tspan x="24" dy="22">odec 구현체 수: &#x27;
grep -rln &#x27;implements GraphQlCursorCodec&#x27; --include=*.java . | wc -l
echo -n &#x27; 그것을 만드는 main 코</tspan><tspan x="24" dy="22">드: &#x27;
grep -rn &#x27;new HmacGraphQlCursorCodec\|GraphQlCursorKeyRing.of(\|GraphQlCursorKeyRing.single(&#x27; --include=*.java . |</tspan><tspan x="24" dy="22"> grep &#x27;/main/&#x27; | grep -v &#x27;HmacGraphQlCursorCodec.java&#x27; | grep -vc &#x27;GraphQlCursorKeyRing.java&#x27;
echo
echo &#x27;# 서명될 커서가</tspan><tspan x="24" dy="22"> 지금 있는가&#x27;
echo -n &#x27; 커넥션 조립기를 만드는 main 코드: &#x27;
grep -rn &#x27;new GraphQlConnectionAssembler&#x27; --include=*.java </tspan><tspan x="24" dy="22">. | grep -c &#x27;/main/&#x27;
echo &#x27; 배포되는 스키마 전체&#x27;
grep -v &#x27;^#&#x27; $M/../../../../../resources/graphql/skeleton.graphqls 2&gt;</tspan><tspan x="24" dy="22">/dev/null || grep -v &#x27;^#&#x27; $G/main/resources/graphql/skeleton.graphqls
echo
echo &#x27;# 그런데 릴리스 게이트가 읽는 매니페스</tspan><tspan x="24" dy="22">트는 이미 승인 목록에 넣어 두었다&#x27;
grep -n -B3 &#x27;SIGNED_CURSOR_CONNECTION&#x27; $M/release/GraphQlStableCapabilityManifest.jav</tspan><tspan x="24" dy="22">a | head -6
echo
echo &#x27;# 그 부재를 고정하는 테스트&#x27;
sed -n &#x27;78,84p&#x27; $T/autoconfigure/GraphQlPolicyRequestPathTest.java
e</tspan><tspan x="24" dy="22">cho &#x27;# 그리고 요구는 남기라고 못 박는 네 번째 케이스&#x27;
sed -n &#x27;100,110p&#x27; $T/autoconfigure/GraphQlPolicyRequestPathTest.</tspan><tspan x="24" dy="22">java
echo
echo &#x27;# 등급표가 두 능력에 매긴 등급&#x27;
grep -n &#x27;cursor 서명\|mutation 멱등성&#x27; adapter/inbound/graphql/CLAUDE.md</tspan><tspan x="24" dy="22"> | cut -c1-70
</tspan></text>
<text x="24" y="442" 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/src</tspan></text>
<text x="24" y="464" 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-02T02:41:05+00:00 · exit 0</tspan></text>
<line x1="24" y1="478" x2="1176" y2="478" stroke="#30363d"/>
<text x="24" y="518" 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="540" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> problems.add(&quot;a cursor signing key is required; unsigned cursors are client-editable&quot;);</tspan></text>
<text x="24" y="562" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 그 검증기는 실제로 돈다 — 자동설정의 호출 지점: 127: validator.validate(properties);</tspan></text>
<text x="24" y="584" 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="606" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 그 키 설정을 소비하는 main 코드</tspan></text>
<text x="24" y="628" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">GraphQlPlatformActuatorEndpoint.java:62: properties.cursor().keyIds(),</tspan></text>
<text x="24" y="650" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">GraphQlPlatformStartupValidator.java:42: if (properties.production() &amp;&amp; properties.cursor().keyIds().isEmpty()) {</tspan></text>
<text x="24" y="672" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 그중 액추에이터 엔드포인트를 만드는 main 코드: 0</tspan></text>
<text x="24" y="694" 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="716" 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="738" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GraphQlCursorCodec 구현체 수: 1</tspan></text>
<text x="24" y="760" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 그것을 만드는 main 코드: 0</tspan></text>
<text x="24" y="782" 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="804" 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="826" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 커넥션 조립기를 만드는 main 코드: 0</tspan></text>
<text x="24" y="848" 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="870" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">type Query {</tspan></text>
<text x="24" y="892" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> &quot;Liveness token for the GraphQL transport — mirrors the web adapter&#x27;s /healthcheck.&quot;</tspan></text>
<text x="24" y="914" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> _health: String!</tspan></text>
<text x="24" y="936" 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="958" 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="980" 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="1002" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">24- &quot;REQUEST_CONTEXT&quot;,</tspan></text>
<text x="24" y="1024" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">25- &quot;SECURITY&quot;,</tspan></text>
<text x="24" y="1046" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">26- &quot;DATALOADER&quot;,</tspan></text>
<text x="24" y="1068" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0">27: &quot;SIGNED_CURSOR_CONNECTION&quot;,</tspan></text>
<text x="24" y="1090" 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="1112" 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="1134" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> assertThat(context)</tspan></text>
<text x="24" y="1156" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> .as(</tspan></text>
<text x="24" y="1178" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> &quot;and nothing signs with it: production refuses to start without a key that &quot;</tspan></text>
<text x="24" y="1200" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> + &quot;protects nothing, which is worse than not asking for one&quot;)</tspan></text>
<text x="24" y="1222" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> .doesNotHaveBean(GraphQlCursorCodec.class);</tspan></text>
<text x="24" y="1244" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> assertThat(context).doesNotHaveBean(GraphQlCursorKeyRing.class);</tspan></text>
<text x="24" y="1266" 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="1288" 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="1310" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> @DisplayName(&quot;the startup validator still demands the key it cannot make anyone use&quot;)</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"> void theValidatorStillDemandsTheUnusedKey() {</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"> GraphQlPlatformSettings production =</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"> GraphQlPlatformSettings.productionDefaults().withCursorKeyIds(Set.of());</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"></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"> assertThat(new GraphQlPlatformStartupValidator().problems(production))</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"> .as(</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"> &quot;kept, not removed: the demand is right and the missing half is the implementation. &quot;</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"> + &quot;When cursor signing reaches the request path, the two cases above invert and &quot;</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"> + &quot;this one stays exactly as it is.&quot;)</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"> .anySatisfy(problem -&gt; assertThat(problem).contains(&quot;cursor signing key&quot;));</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"># 등급표가 두 능력에 매긴 등급</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">149:| cursor 서명 (`pagination/`) | `modelled` | `HmacGraphQlCursorC</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">150:| mutation 멱등성 (`mutation/`) | `modelled` | `GraphQlMutation</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">168:- **cursor 서명이 요청 경로에 없다 (GQL-INT-003).** `bac</tspan></text>
</svg>