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>
177 lines
32 KiB
XML
177 lines
32 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="3120" viewBox="0 0 1200 3120" 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="3118" 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
|
|
set -o pipefail
|
|
W=src/adapter/inbound/websocket/src/main/java/dev/caskeleton/adapter/inbound/websocket
|
|
strip() </tspan><tspan x="24" dy="22">{ sed -E 's#^src/[^:]*/websocket/([^:]*/)?([^:/]+):([0-9]+):# \2:\3 #'; }
|
|
cut_range() { awk -v f="$2" -v a="$3" -v b="$</tspan><tspan x="24" dy="22">4" 'NR>=a && NR<=b {printf " %s:%d %s\n", f, NR, $0}' "$1"; }
|
|
|
|
echo "# ca-skeleton.websocket 어댑터가 등록하는 인터셉터</tspan><tspan x="24" dy="22"> 둘"
|
|
for f in AuthenticatedHandshakeInterceptor WebSocketInboundAuthorizationInterceptor; do
|
|
printf " %-44s %s 줄\n" </tspan><tspan x="24" dy="22">"$f.java" "$(wc -l < $W/stomp/$f.java)"
|
|
done
|
|
awk 'NR>=30 && NR<=60 && (/Interceptor/ || /addInterceptors/ || /configureC</tspan><tspan x="24" dy="22">lientInboundChannel/ || /ConditionalOnProperty/) {printf " stomp/WebSocketConfig.java:%d %s\n", NR, $0}' $W/stomp/WebSo</tspan><tspan x="24" dy="22">cketConfig.java
|
|
|
|
echo
|
|
echo "# 같은 채널에 붙는 세 번째 인터셉터와 그 게이트"
|
|
printf " %-44s %s 줄\n" "advanced/stomp/St</tspan><tspan x="24" dy="22">ompSecurityInterceptor.java" "$(wc -l < $W/advanced/stomp/StompSecurityInterceptor.java)"
|
|
cut_range $W/advanced/stomp/St</tspan><tspan x="24" dy="22">ompConfiguration.java advanced/stomp/StompConfiguration.java 24 27
|
|
awk 'NR>=33 && NR<=41 && (/securityInterceptor/ || /c</tspan><tspan x="24" dy="22">onfigureClientInboundChannel/) {printf " advanced/stomp/StompConfiguration.java:%d %s\n", NR, $0}' $W/advanced/stomp/St</tspan><tspan x="24" dy="22">ompConfiguration.java
|
|
|
|
echo
|
|
echo "# 이 모듈이 출하 런타임 그래프에 있는가"
|
|
awk 'NR>=196 && NR<=200 {printf " app-bootst</tspan><tspan x="24" dy="22">rap/build.gradle:%d %s\n", NR, $0}' src/app-bootstrap/build.gradle
|
|
cut_range src/adapter/inbound/websocket/CLAUDE.md web</tspan><tspan x="24" dy="22">socket/CLAUDE.md 21 23
|
|
|
|
echo
|
|
echo "# 정책 계층 — 네 패키지의 파일과 선언 형태와 줄 수"
|
|
for d in security authz idempote</tspan><tspan x="24" dy="22">ncy budget; do
|
|
for f in $(find $W/$d -name '*.java' | sort); do
|
|
k=$(grep -oE 'public (final class|record|enum|inte</tspan><tspan x="24" dy="22">rface|class)' $f | head -1 || true)
|
|
printf " %-12s %-38s %-16s %s 줄\n" "$d" "$(basename $f)" "${k#public }" "$(wc </tspan><tspan x="24" dy="22">-l < $f)"
|
|
done
|
|
done
|
|
printf " 합계 %s 파일\n" "$(find $W/security $W/authz $W/idempotency $W/budget -name '*.java' | w</tspan><tspan x="24" dy="22">c -l)"
|
|
|
|
echo
|
|
echo "# 그 열 타입을 stomp 패키지가 참조하는가, 그리고 어디서 생성되는가"
|
|
for f in $(find $W/security $W/au</tspan><tspan x="24" dy="22">thz $W/idempotency $W/budget -name '*.java' | sed 's|.*/||;s|\.java$||' | sort); do
|
|
s=$(grep -rl "\b$f\b" $W/stomp 2>/</tspan><tspan x="24" dy="22">dev/null | wc -l || true)
|
|
n=$(git grep -c "new $f(" -- 'src/**/*.java' 2>/dev/null | grep '/main/' | wc -l || true)
|
|
</tspan><tspan x="24" dy="22">b=$(git grep -l "$f " -- 'src/**/*.java' 2>/dev/null | grep '/main/' | xargs grep -l '@Bean' 2>/dev/null | wc -l || true</tspan><tspan x="24" dy="22">)
|
|
printf " %-34s stomp참조=%s main생성=%s 같은파일에@Bean있는main=%s\n" "$f" "$s" "$n" "$b"
|
|
done
|
|
|
|
echo
|
|
echo "# 원문</tspan><tspan x="24" dy="22">이 22 로 적은 수의 출처"
|
|
grep -n '^# Sub-scope 04' /shared/document-detail/clean-architecture-backend-template/analysis/</tspan><tspan x="24" dy="22">17-adapter-inbound-websocket.md \
|
|
| cut -c1-150 | sed 's#^# analysis/17:#'
|
|
t=0; for d in security authz idempotency b</tspan><tspan x="24" dy="22">udget error observability admin release; do
|
|
n=$(find $W/$d -name '*.java' | wc -l); t=$((t+n)); printf " %-14s %s\n" </tspan><tspan x="24" dy="22">"$d" "$n"
|
|
done
|
|
echo " 여덟 패키지 합계 $t"
|
|
|
|
echo
|
|
echo "# MessageAuthorizationPolicy 를 받는 자리와 그 받는 쪽의 생성자"</tspan><tspan x="24" dy="22">
|
|
grep -n 'public ' $W/authz/MessageAuthorizationPolicy.java | sed -E 's#^([0-9]+): *# authz/MessageAuthorizationPolicy.</tspan><tspan x="24" dy="22">java:\1 #'
|
|
git grep -n 'MessageAuthorizationPolicy' -- 'src/**/*.java' | grep '/main/' | grep -v '/MessageAuthorization</tspan><tspan x="24" dy="22">Policy.java' | strip
|
|
grep -n 'public WebSocketPlatformStartupValidator\|public void validate\|undeclaredAmong' $W/config</tspan><tspan x="24" dy="22">/WebSocketPlatformStartupValidator.java \
|
|
| sed -E 's#^([0-9]+): *# config/WebSocketPlatformStartupValidator.java:\1 </tspan><tspan x="24" dy="22"> #'
|
|
git grep -n 'new WebSocketPlatformStartupValidator(' -- 'src/**/*.java' | strip
|
|
|
|
echo
|
|
echo "# 리프의 CLAUDE.md 가 적</tspan><tspan x="24" dy="22">는 범위와 인바운드 정책 절 전체"
|
|
cut_range src/adapter/inbound/websocket/CLAUDE.md websocket/CLAUDE.md 15 18
|
|
cut_range s</tspan><tspan x="24" dy="22">rc/adapter/inbound/websocket/CLAUDE.md websocket/CLAUDE.md 52 60
|
|
</tspan></text>
|
|
<text x="24" y="772" 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="794" 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-04T00:20:53+00:00 · exit 0</tspan></text>
|
|
<line x1="24" y1="808" x2="1176" y2="808" stroke="#30363d"/>
|
|
<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"># ca-skeleton.websocket 어댑터가 등록하는 인터셉터 둘</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"> AuthenticatedHandshakeInterceptor.java 34 줄</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"> WebSocketInboundAuthorizationInterceptor.java 53 줄</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"> stomp/WebSocketConfig.java:32 @ConditionalOnProperty(prefix = "ca-skeleton.websocket", name = "enabled", havingValue = "true")</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"> stomp/WebSocketConfig.java:36 private final AuthenticatedHandshakeInterceptor handshakeInterceptor =</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"> stomp/WebSocketConfig.java:37 new AuthenticatedHandshakeInterceptor();</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"> stomp/WebSocketConfig.java:40 private final WebSocketInboundAuthorizationInterceptor inboundAuthorization;</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"> stomp/WebSocketConfig.java:44 this.inboundAuthorization = new WebSocketInboundAuthorizationInterceptor(properties);</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"> stomp/WebSocketConfig.java:53 .addInterceptors(handshakeInterceptor)</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"> stomp/WebSocketConfig.java:58 public void configureClientInboundChannel(ChannelRegistration registration) {</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"></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"> advanced/stomp/StompSecurityInterceptor.java 96 줄</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"> advanced/stomp/StompConfiguration.java:24 @ConditionalOnProperty(</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"> advanced/stomp/StompConfiguration.java:25 prefix = "app.websocket-platform.advanced.stomp",</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"> advanced/stomp/StompConfiguration.java:26 name = "enabled",</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"> advanced/stomp/StompConfiguration.java:27 havingValue = "true")</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"> advanced/stomp/StompConfiguration.java:33 public StompConfiguration(StompProfile profile, StompSecurityInterceptor securityI</tspan><tspan x="24" dy="22">nterceptor) {</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"> advanced/stomp/StompConfiguration.java:35 this.securityInterceptor = securityInterceptor;</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"> advanced/stomp/StompConfiguration.java:39 public void configureClientInboundChannel(ChannelRegistration registration) {</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"> advanced/stomp/StompConfiguration.java:40 registration.interceptors(securityInterceptor);</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"></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"> app-bootstrap/build.gradle:196 // Explicit qualification-only composition. These projects remain absent from main</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"> app-bootstrap/build.gradle:197 // api/implementation/compileOnly/runtimeOnly and therefore from both shipped runtime graph</tspan><tspan x="24" dy="22">s.</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"> app-bootstrap/build.gradle:198 conditionalTransportTestImplementation project(':adapter:inbound:graphql')</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"> app-bootstrap/build.gradle:199 conditionalTransportTestImplementation project(':adapter:inbound:grpc')</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"> app-bootstrap/build.gradle:200 conditionalTransportTestImplementation project(':adapter:inbound:websocket')</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"> websocket/CLAUDE.md:21 The module is absent from both shipped runtime compositions. Being registered and tested does not</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"> websocket/CLAUDE.md:22 activate it. A future composition must deliberately add the registered dependency and set</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"> websocket/CLAUDE.md:23 `ca-skeleton.websocket.enabled=true` with explicit safe origins.</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"># 정책 계층 — 네 패키지의 파일과 선언 형태와 줄 수</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"> security WebSocketAuthenticationProfile.java enum 57 줄</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"> security WebSocketConnectionTicket.java record 77 줄</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"> security WebSocketOriginPolicy.java final class 116 줄</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"> security WebSocketTicketStore.java interface 32 줄</tspan></text>
|
|
<text x="24" y="1706" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz MessageAuthorizationPolicy.java final class 93 줄</tspan></text>
|
|
<text x="24" y="1728" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> idempotency CommandReconciliation.java interface 84 줄</tspan></text>
|
|
<text x="24" y="1750" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> idempotency CommittedResultLedger.java interface 61 줄</tspan></text>
|
|
<text x="24" y="1772" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> idempotency WebSocketCommandKey.java record 40 줄</tspan></text>
|
|
<text x="24" y="1794" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> idempotency WebSocketCommandOutcome.java enum 33 줄</tspan></text>
|
|
<text x="24" y="1816" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> budget WebSocketConnectionBudget.java record 103 줄</tspan></text>
|
|
<text x="24" y="1838" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 합계 10 파일</tspan></text>
|
|
<text x="24" y="1860" 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="1882" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 그 열 타입을 stomp 패키지가 참조하는가, 그리고 어디서 생성되는가</tspan></text>
|
|
<text x="24" y="1904" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> CommandReconciliation stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="1926" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> CommittedResultLedger stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="1948" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> MessageAuthorizationPolicy stomp참조=0 main생성=1 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="1970" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketAuthenticationProfile stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="1992" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketCommandKey stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="2014" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketCommandOutcome stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="2036" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketConnectionBudget stomp참조=0 main생성=1 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="2058" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketConnectionTicket stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="2080" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketOriginPolicy stomp참조=0 main생성=1 같은파일에@Bean있는main=1</tspan></text>
|
|
<text x="24" y="2102" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketTicketStore stomp참조=0 main생성=0 같은파일에@Bean있는main=0</tspan></text>
|
|
<text x="24" y="2124" 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="2146" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 원문이 22 로 적은 수의 출처</tspan></text>
|
|
<text x="24" y="2168" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> analysis/17:235:# Sub-scope 04 — `security` + `authz` + `idempotency` + `budget` + `error` + `observability` + `admin` + `rel</tspan><tspan x="24" dy="22">ease` (31 files, main 22 + test 9)</tspan></text>
|
|
<text x="24" y="2212" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> security 4</tspan></text>
|
|
<text x="24" y="2234" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz 1</tspan></text>
|
|
<text x="24" y="2256" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> idempotency 4</tspan></text>
|
|
<text x="24" y="2278" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> budget 1</tspan></text>
|
|
<text x="24" y="2300" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> error 5</tspan></text>
|
|
<text x="24" y="2322" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> observability 2</tspan></text>
|
|
<text x="24" y="2344" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> admin 2</tspan></text>
|
|
<text x="24" y="2366" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> release 3</tspan></text>
|
|
<text x="24" y="2388" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 여덟 패키지 합계 22</tspan></text>
|
|
<text x="24" y="2410" 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="2432" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># MessageAuthorizationPolicy 를 받는 자리와 그 받는 쪽의 생성자</tspan></text>
|
|
<text x="24" y="2454" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz/MessageAuthorizationPolicy.java:26 public final class MessageAuthorizationPolicy {</tspan></text>
|
|
<text x="24" y="2476" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz/MessageAuthorizationPolicy.java:39 public static MessageAuthorizationPolicy of(Map<WebSocketMessageType, Set<String>> r</tspan><tspan x="24" dy="22">equirements) {</tspan></text>
|
|
<text x="24" y="2520" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz/MessageAuthorizationPolicy.java:57 public boolean permits(WebSocketMessageType type, Set<String> heldAuthorities) {</tspan></text>
|
|
<text x="24" y="2542" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz/MessageAuthorizationPolicy.java:70 public boolean declares(WebSocketMessageType type) {</tspan></text>
|
|
<text x="24" y="2564" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz/MessageAuthorizationPolicy.java:81 public java.util.List<WebSocketMessageType> undeclaredAmong(</tspan></text>
|
|
<text x="24" y="2586" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> authz/MessageAuthorizationPolicy.java:90 public Map<WebSocketMessageType, Set<String>> requirements() {</tspan></text>
|
|
<text x="24" y="2608" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketPlatformStartupValidator.java:3 import dev.caskeleton.adapter.inbound.websocket.authz.MessageAuthorizationPolicy;</tspan></text>
|
|
<text x="24" y="2630" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketPlatformStartupValidator.java:56 MessageAuthorizationPolicy authorization,</tspan></text>
|
|
<text x="24" y="2652" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> config/WebSocketPlatformStartupValidator.java:38 public WebSocketPlatformStartupValidator(boolean productionProfile) {</tspan></text>
|
|
<text x="24" y="2674" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> config/WebSocketPlatformStartupValidator.java:53 public void validate(</tspan></text>
|
|
<text x="24" y="2696" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> config/WebSocketPlatformStartupValidator.java:106 authorization.undeclaredAmong(</tspan></text>
|
|
<text x="24" y="2718" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketPlatformStartupValidatorTest.java:40 new WebSocketPlatformStartupValidator(true);</tspan></text>
|
|
<text x="24" y="2740" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> WebSocketPlatformStartupValidatorTest.java:42 new WebSocketPlatformStartupValidator(false);</tspan></text>
|
|
<text x="24" y="2762" 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="2784" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 리프의 CLAUDE.md 가 적는 범위와 인바운드 정책 절 전체</tspan></text>
|
|
<text x="24" y="2806" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:15 - Opt-in STOMP over SockJS transport configuration and typed validation.</tspan></text>
|
|
<text x="24" y="2828" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:16 - HTTP-handshake principal enforcement and client-inbound STOMP destination authorization.</tspan></text>
|
|
<text x="24" y="2850" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:17 - Fixed client-safe STOMP `ERROR` mapping.</tspan></text>
|
|
<text x="24" y="2872" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:18 - Best-effort in-process event push through an explicit `LiveEventProjector` allowlist.</tspan></text>
|
|
<text x="24" y="2894" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:52 ## Inbound policy</tspan></text>
|
|
<text x="24" y="2916" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:53 </tspan></text>
|
|
<text x="24" y="2938" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:54 - The HTTP upgrade must already have a nonblank `Principal`; the adapter does not authenticate</tspan></text>
|
|
<text x="24" y="2960" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:55 credentials itself.</tspan></text>
|
|
<text x="24" y="2982" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:56 - STOMP `SUBSCRIBE` is allowed only for the configured broadcast destination.</tspan></text>
|
|
<text x="24" y="3004" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:57 - Authenticated `SEND` is allowed only below `/app/**`.</tspan></text>
|
|
<text x="24" y="3026" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:58 - Client `SEND` to `/topic/**` and other destinations is rejected.</tspan></text>
|
|
<text x="24" y="3048" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:59 - Every client-visible processing failure becomes the fixed</tspan></text>
|
|
<text x="24" y="3070" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> websocket/CLAUDE.md:60 `WEBSOCKET_REQUEST_REJECTED` ERROR code with an empty payload.</tspan></text>
|
|
</svg>
|