Files
document-haness/docs/clean-architecture-backend-template/final/evidence/rendered/a20-f010-grpcoutcomereplay.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

255 lines
49 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="4836" viewBox="0 0 1200 4836" 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="4834" 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">$ #!/bin/bash
set -euo pipefail
cd /shared/codebase/clean-architecture-backend-template
R=src/grpc/grpc-policy/src/main/</tspan><tspan x="24" dy="22">java/dev/caskeleton/grpc/idempotency/GrpcOutcomeReplay.java
D=src/grpc-advanced/grpc-advanced-streaming/src/main/java/de</tspan><tspan x="24" dy="22">v/caskeleton/grpc/advanced/streaming/GrpcClientMessageDeduplicator.java
test -f &quot;$R&quot;; test -f &quot;$D&quot;
CPP=$(cat /tmp/cp-grp</tspan><tspan x="24" dy="22">c-policy.txt)
# 검색이 헛돌면 0 을 제조하지 않고 죽는다. rc 0=매치, 1=매치없음, 그 밖=오류.
refs() {
local name=$1 se</tspan><tspan x="24" dy="22">lf=$2 out rc
out=$(git grep -l &quot;$name&quot; -- &#x27;src/**/*.java&#x27;); rc=$?
[ $rc -le 1 ] || { echo &quot;SEARCH FAILED rc=$rc for </tspan><tspan x="24" dy="22">$name&quot; &gt;&amp;2; exit 2; }
printf &#x27;%s\n&#x27; &quot;$out&quot; | { grep &#x27;/main/&#x27; || true; } | { grep -v &quot;/$self&quot; || true; } | grep -c . ||</tspan><tspan x="24" dy="22"> true
}
echo &quot;# 저장소 전체 ($(wc -l &lt; $R) 줄)&quot;
awk &#x27;{printf &quot; GrpcOutcomeReplay.java:%d %s\n&quot;, NR, $0}&#x27; $R
echo
echo</tspan><tspan x="24" dy="22"> &quot;# 그 맵에 가해지는 연산 전부&quot;
grep -n &#x27;storedOutcomes&#x27; $R | sed -E &#x27;s#^([0-9]+): *# GrpcOutcomeReplay.java:\1 #&#x27;
e</tspan><tspan x="24" dy="22">cho &quot; 클래스와 필드의 접근 한정 :&quot;
grep -nE &#x27;public final class|private final ConcurrentMap&#x27; $R | sed -E &#x27;s#^([0-9]+)</tspan><tspan x="24" dy="22">: *# GrpcOutcomeReplay.java:\1 #&#x27;
N=&#x27;\.remove|\.clear|removeIf|computeIfPresent|\.merge|entrySet|keySet|evict|expire</tspan><tspan x="24" dy="22">|TTL|Duration|Instant|maximumSize|Caffeine|LinkedHashMap|WeakReference|SoftReference|Cleaner&#x27;
echo &quot; 줄이는 연산 이름</tspan><tspan x="24" dy="22"> 열여덟을 통틀어 : $({ grep -cE &quot;$N&quot; $R || true; }) 건&quot;
echo &quot; 같은 검색을 대조 파일에 걸면 : $({ grep -cE &quot;$N&quot; $D</tspan><tspan x="24" dy="22"> || true; }) 건 (검색이 헛돌지 않음을 보인다)&quot;
echo
echo &quot;# 크기 상한이 실제로 거부하는가&quot;
cd /tmp/probe5
javac -nowar</tspan><tspan x="24" dy="22">n -encoding UTF-8 -cp &quot;$CPP&quot; -d . OutcomeReplayProbe.java
java -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -cp &quot;.:</tspan><tspan x="24" dy="22">$CPP&quot; OutcomeReplayProbe
cd /shared/codebase/clean-architecture-backend-template
echo
echo &quot;# 이 타입이 나오는 파일 전</tspan><tspan x="24" dy="22">부 — 경로 제한 없이, 절단 없이&quot;
git grep -l &#x27;GrpcOutcomeReplay&#x27; | sed &#x27;s/^/ /&#x27;
echo &quot; 자기 파일 밖 main 참조 : $(refs</tspan><tspan x="24" dy="22"> GrpcOutcomeReplay GrpcOutcomeReplay.java) 개&quot;
echo &quot; [대조] 같은 패키지의 GrpcIdempotencyDecision : $(refs GrpcIdempot</tspan><tspan x="24" dy="22">encyDecision GrpcIdempotencyDecision.java) 개 (0 이 아니어야 정상)&quot;
echo &quot; git 이 추적하지 않는 변경 : $(git status --</tspan><tspan x="24" dy="22">porcelain | grep -c . || true) 건&quot;
echo &quot; .java 밖에서 이 이름을 쓰는 파일 : $({ git grep -l &#x27;GrpcOutcomeReplay&#x27; -- &#x27;:!</tspan><tspan x="24" dy="22">*.java&#x27; || true; } | grep -c . || true) 개&quot;
echo
echo &quot;# 같은 패키지의 인터셉터는 재생을 어떻게 판정하는가&quot;
I=src/grpc/</tspan><tspan x="24" dy="22">grpc-policy/src/main/java/dev/caskeleton/grpc/idempotency/GrpcIdempotencyInterceptor.java
awk &#x27;NR&gt;=95 &amp;&amp; NR&lt;=110 {printf</tspan><tspan x="24" dy="22"> &quot; GrpcIdempotencyInterceptor.java:%d %s\n&quot;, NR, $0}&#x27; $I
echo &quot; 그 파일이 GrpcOutcomeReplay 를 부르는가 : $({ grep -c </tspan><tspan x="24" dy="22">&#x27;GrpcOutcomeReplay&#x27; $I || true; }) 건&quot;
echo
echo &quot;# 자바독이 이 타입을 무엇이라고 적는가&quot;
awk &#x27;NR&gt;=7 &amp;&amp; NR&lt;=16 {printf </tspan><tspan x="24" dy="22">&quot; GrpcOutcomeReplay.java:%d %s\n&quot;, NR, $0}&#x27; $R
echo
echo &quot;# 대조 클래스에 같은 잣대를 댄다 ($(wc -l &lt; $D) 줄)&quot;
echo &quot; </tspan><tspan x="24" dy="22"> 이 파일 : $R&quot;
echo &quot; 대조 파일 : $D&quot;
awk &#x27;NR&gt;=11 &amp;&amp; NR&lt;=14 {printf &quot; GrpcClientMessageDeduplicator.java:%d %s\n&quot;, N</tspan><tspan x="24" dy="22">R, $0}&#x27; $D
awk &#x27;NR&gt;=16 &amp;&amp; NR&lt;=23 {printf &quot; GrpcClientMessageDeduplicator.java:%d %s\n&quot;, NR, $0}&#x27; $D
echo &quot; 적용된 메시</tspan><tspan x="24" dy="22">지마다 무엇을 넣는가 :&quot;
awk &#x27;NR&gt;=70 &amp;&amp; NR&lt;=77 {printf &quot; GrpcClientMessageDeduplicator.java:%d %s\n&quot;, NR, $0}&#x27; $D
echo &quot;</tspan><tspan x="24" dy="22"> 정리 경로와 그 호출자 :&quot;
git grep -n &#x27;endSession&#x27; -- &#x27;src/**/*.java&#x27; \
| sed -E &#x27;s#^src/[^:]*/src/(main|test)/java/d</tspan><tspan x="24" dy="22">ev/caskeleton/[^:]*/([^:/]+):([0-9]+):# \1 · \2:\3 #&#x27;
echo &quot; 그 대조 클래스의 자기 파일 밖 main 참조 : $(refs Grp</tspan><tspan x="24" dy="22">cClientMessageDeduplicator GrpcClientMessageDeduplicator.java) 개&quot;
echo
echo &quot;# 두 클래스가 같은 family 인가&quot;
echo &quot; m</tspan><tspan x="24" dy="22">odules.json 의 family 키 : $({ grep -c &#x27;family&#x27; src/config/architecture/modules.json || true; }) 건&quot;
awk &#x27;NR&gt;=1 &amp;&amp; NR&lt;=3</tspan><tspan x="24" dy="22"> {printf &quot; src/grpc/CLAUDE.md:%d %s\n&quot;, NR, $0}&#x27; src/grpc/CLAUDE.md
awk &#x27;NR&gt;=3 &amp;&amp; NR&lt;=9 {printf &quot; src/grpc-advanced/CL</tspan><tspan x="24" dy="22">AUDE.md:%d %s\n&quot;, NR, $0}&#x27; src/grpc-advanced/CLAUDE.md
grep -nE &#x27;grpc:\*.*→|Stable leaf&#x27; src/grpc-advanced/CLAUDE.md \
</tspan><tspan x="24" dy="22"> | sed -E &#x27;s#^([0-9]+): *# src/grpc-advanced/CLAUDE.md:\1 #&#x27;
</tspan></text>
<text x="24" y="816" 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="838" 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-04T03:20:56+00:00 · exit 0</tspan></text>
<line x1="24" y1="852" x2="1176" y2="852" stroke="#30363d"/>
<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"># 저장소 전체 (62 줄)</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"> GrpcOutcomeReplay.java:1 package dev.caskeleton.grpc.idempotency;</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"> GrpcOutcomeReplay.java:2 </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"> GrpcOutcomeReplay.java:3 import java.util.Optional;</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"> GrpcOutcomeReplay.java:4 import java.util.concurrent.ConcurrentHashMap;</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"> GrpcOutcomeReplay.java:5 import java.util.concurrent.ConcurrentMap;</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"> GrpcOutcomeReplay.java:6 </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"> GrpcOutcomeReplay.java:7 /**</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"> GrpcOutcomeReplay.java:8 * Returns the stored answer for an operation that already committed.</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"> GrpcOutcomeReplay.java:9 *</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"> GrpcOutcomeReplay.java:10 * &lt;p&gt;Separated from the ledger because the two have different size problems. The ledger row must</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"> GrpcOutcomeReplay.java:11 * stay small and is written inside the business transaction; a response may be large and is only</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"> GrpcOutcomeReplay.java:12 * needed if somebody actually retries. So the ledger stores a reference and this resolves it,</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"> GrpcOutcomeReplay.java:13 * against whatever the deployment chose — a small inline store, an object store, or a re-read of</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"> GrpcOutcomeReplay.java:14 * the committed resource.</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"> GrpcOutcomeReplay.java:15 */</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"> GrpcOutcomeReplay.java:16 public final class GrpcOutcomeReplay {</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"> GrpcOutcomeReplay.java:17 </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"> GrpcOutcomeReplay.java:18 private final ConcurrentMap&lt;String, byte[]&gt; storedOutcomes = new ConcurrentHashMap&lt;&gt;();</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"> GrpcOutcomeReplay.java:19 private final int maxInlineBytes;</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"> GrpcOutcomeReplay.java:20 </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"> GrpcOutcomeReplay.java:21 /**</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"> GrpcOutcomeReplay.java:22 * @param maxInlineBytes the largest response stored inline; anything larger must be kept behind a</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"> GrpcOutcomeReplay.java:23 * reference in an object store instead, which is why {@link #store} refuses it rather than</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"> GrpcOutcomeReplay.java:24 * silently truncating</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"> GrpcOutcomeReplay.java:25 */</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"> GrpcOutcomeReplay.java:26 public GrpcOutcomeReplay(int maxInlineBytes) {</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"> GrpcOutcomeReplay.java:27 if (maxInlineBytes &lt; 1) {</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"> GrpcOutcomeReplay.java:28 throw new IllegalArgumentException(&quot;an inline outcome store needs a positive size limit&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"> GrpcOutcomeReplay.java:29 }</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"> GrpcOutcomeReplay.java:30 this.maxInlineBytes = maxInlineBytes;</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"> GrpcOutcomeReplay.java:31 }</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"> GrpcOutcomeReplay.java:32 </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"> GrpcOutcomeReplay.java:33 /** Stores a response under {@code outcomeReference}. */</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"> GrpcOutcomeReplay.java:34 public void store(String outcomeReference, byte[] serializedResponse) {</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"> GrpcOutcomeReplay.java:35 if (outcomeReference == null || outcomeReference.isBlank()) {</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"> GrpcOutcomeReplay.java:36 throw new IllegalArgumentException(&quot;an outcome needs a reference to be stored under&quot;);</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"> GrpcOutcomeReplay.java:37 }</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"> GrpcOutcomeReplay.java:38 if (serializedResponse == null) {</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"> GrpcOutcomeReplay.java:39 throw new IllegalArgumentException(&quot;an outcome needs a serialized response&quot;);</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"> GrpcOutcomeReplay.java: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"> GrpcOutcomeReplay.java:41 if (serializedResponse.length &gt; maxInlineBytes) {</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"> GrpcOutcomeReplay.java:42 throw new IllegalArgumentException(</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"> GrpcOutcomeReplay.java:43 &quot;response of &quot;</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"> GrpcOutcomeReplay.java:44 + serializedResponse.length</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"> GrpcOutcomeReplay.java:45 + &quot; bytes exceeds the inline outcome limit of &quot;</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"> GrpcOutcomeReplay.java:46 + maxInlineBytes</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"> GrpcOutcomeReplay.java:47 + &quot;; store it behind an object reference instead&quot;);</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"> GrpcOutcomeReplay.java:48 }</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"> GrpcOutcomeReplay.java:49 storedOutcomes.put(outcomeReference, serializedResponse.clone());</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"> GrpcOutcomeReplay.java:50 }</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"> GrpcOutcomeReplay.java:51 </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"> GrpcOutcomeReplay.java:52 /** The stored response, if there is one. */</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"> GrpcOutcomeReplay.java:53 public Optional&lt;byte[]&gt; replay(String outcomeReference) {</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"> GrpcOutcomeReplay.java:54 byte[] stored = storedOutcomes.get(outcomeReference);</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"> GrpcOutcomeReplay.java:55 return Optional.ofNullable(stored).map(byte[]::clone);</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"> GrpcOutcomeReplay.java:56 }</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"> GrpcOutcomeReplay.java:57 </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"> GrpcOutcomeReplay.java:58 /** How many outcomes are held. */</tspan></text>
<text x="24" y="2190" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:59 public int size() {</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"> GrpcOutcomeReplay.java:60 return storedOutcomes.size();</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"> GrpcOutcomeReplay.java:61 }</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"> GrpcOutcomeReplay.java:62 }</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"></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"># 그 맵에 가해지는 연산 전부</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"> GrpcOutcomeReplay.java:18 private final ConcurrentMap&lt;String, byte[]&gt; storedOutcomes = new ConcurrentHashMap&lt;&gt;();</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"> GrpcOutcomeReplay.java:49 storedOutcomes.put(outcomeReference, serializedResponse.clone());</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"> GrpcOutcomeReplay.java:54 byte[] stored = storedOutcomes.get(outcomeReference);</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"> GrpcOutcomeReplay.java:60 return storedOutcomes.size();</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"> GrpcOutcomeReplay.java:16 public final class GrpcOutcomeReplay {</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"> GrpcOutcomeReplay.java:18 private final ConcurrentMap&lt;String, byte[]&gt; storedOutcomes = new ConcurrentHashMap&lt;&gt;();</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"> 줄이는 연산 이름 열여덟을 통틀어 : 0 건</tspan></text>
<text x="24" y="2498" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 같은 검색을 대조 파일에 걸면 : 5 건 (검색이 헛돌지 않음을 보인다)</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"></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"># 크기 상한이 실제로 거부하는가</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"> 16 바이트 저장 후 size : 1</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"> 17 바이트 저장 : response of 17 bytes exceeds the inline outcome limit of 16; store it behind an object reference instead</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"> 거부 후 size : 1</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"> 서로 다른 키 1000 개 저장 후 size : 1001</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"> 그 1000 개를 모두 replay 한 뒤 : 1001</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"> 같은 키로 1000 번 덮어쓴 뒤 : 1001</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"></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"># 이 타입이 나오는 파일 전부 — 경로 제한 없이, 절단 없이</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"> docs/2026-08-13-grpc-type-safe-rpc-platform-implementation-plan.md</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"> src/grpc/grpc-policy/src/main/java/dev/caskeleton/grpc/idempotency/GrpcOutcomeReplay.java</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"> src/grpc/grpc-policy/src/test/java/dev/caskeleton/grpc/idempotency/GrpcIdempotencyInterceptorTest.java</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"> 자기 파일 밖 main 참조 : 0 개</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"> [대조] 같은 패키지의 GrpcIdempotencyDecision : 1 개 (0 이 아니어야 정상)</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"> git 이 추적하지 않는 변경 : 0 건</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"> .java 밖에서 이 이름을 쓰는 파일 : 1 개</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"></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"># 같은 패키지의 인터셉터는 재생을 어떻게 판정하는가</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"> GrpcIdempotencyInterceptor.java:95 if (!record.sameRequestAs(requestFingerprint)) {</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"> GrpcIdempotencyInterceptor.java:96 return GrpcIdempotencyDecision.fingerprintMismatch();</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"> GrpcIdempotencyInterceptor.java:97 }</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"> GrpcIdempotencyInterceptor.java:98 if (record.state() == GrpcOperationLedgerState.COMMITTED) {</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"> GrpcIdempotencyInterceptor.java:99 return GrpcIdempotencyDecision.replay(</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"> GrpcIdempotencyInterceptor.java:100 record</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"> GrpcIdempotencyInterceptor.java:101 .outcomeReference()</tspan></text>
<text x="24" y="3092" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:102 .orElseThrow(</tspan></text>
<text x="24" y="3114" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:103 () -&gt;</tspan></text>
<text x="24" y="3136" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:104 new IllegalStateException(</tspan></text>
<text x="24" y="3158" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:105 &quot;COMMITTED ledger record for &#x27;&quot;</tspan></text>
<text x="24" y="3180" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:106 + identity.storageKey()</tspan></text>
<text x="24" y="3202" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:107 + &quot;&#x27; has no outcome reference to replay&quot;)));</tspan></text>
<text x="24" y="3224" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:108 }</tspan></text>
<text x="24" y="3246" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:109 if (record.state() == GrpcOperationLedgerState.FAILED_TERMINAL) {</tspan></text>
<text x="24" y="3268" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcIdempotencyInterceptor.java:110 // A terminal failure is not replayed: the caller asked for the operation, it did no</tspan><tspan x="24" dy="22">t happen,</tspan></text>
<text x="24" y="3312" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 그 파일이 GrpcOutcomeReplay 를 부르는가 : 0 건</tspan></text>
<text x="24" y="3334" 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="3356" 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="3378" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:7 /**</tspan></text>
<text x="24" y="3400" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:8 * Returns the stored answer for an operation that already committed.</tspan></text>
<text x="24" y="3422" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:9 *</tspan></text>
<text x="24" y="3444" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:10 * &lt;p&gt;Separated from the ledger because the two have different size problems. The ledger row must</tspan></text>
<text x="24" y="3466" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:11 * stay small and is written inside the business transaction; a response may be large and is only</tspan></text>
<text x="24" y="3488" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:12 * needed if somebody actually retries. So the ledger stores a reference and this resolves it,</tspan></text>
<text x="24" y="3510" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:13 * against whatever the deployment chose — a small inline store, an object store, or a re-read of</tspan></text>
<text x="24" y="3532" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:14 * the committed resource.</tspan></text>
<text x="24" y="3554" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:15 */</tspan></text>
<text x="24" y="3576" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcOutcomeReplay.java:16 public final class GrpcOutcomeReplay {</tspan></text>
<text x="24" y="3598" 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="3620" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 대조 클래스에 같은 잣대를 댄다 (123 줄)</tspan></text>
<text x="24" y="3642" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 이 파일 : src/grpc/grpc-policy/src/main/java/dev/caskeleton/grpc/idempotency/GrpcOutcomeReplay.java</tspan></text>
<text x="24" y="3664" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> 대조 파일 : src/grpc-advanced/grpc-advanced-streaming/src/main/java/dev/caskeleton/grpc/advanced/streaming/GrpcClientMessageDe</tspan><tspan x="24" dy="22">duplicator.java</tspan></text>
<text x="24" y="3708" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:11 * &lt;p&gt;Checkpoint-based rather than a set of seen keys. A set grows without bound for the</tspan><tspan x="24" dy="22"> life of a</tspan></text>
<text x="24" y="3752" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:12 * session and answers &quot;have I seen this&quot; — which is not quite the question. The questi</tspan><tspan x="24" dy="22">on is &quot;has</tspan></text>
<text x="24" y="3796" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:13 * this been applied&quot;, and a monotonic applied-sequence answers it in constant space and</tspan><tspan x="24" dy="22"> survives</tspan></text>
<text x="24" y="3840" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:14 * the process restart that a set does not.</tspan></text>
<text x="24" y="3862" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:16 * &lt;p&gt;Replayed outcomes are kept for the small window after the checkpoint, so a duplica</tspan><tspan x="24" dy="22">te that</tspan></text>
<text x="24" y="3906" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:17 * arrives before the checkpoint advances gets the original answer rather than being rea</tspan><tspan x="24" dy="22">pplied.</tspan></text>
<text x="24" y="3950" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:18 */</tspan></text>
<text x="24" y="3972" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:19 public final class GrpcClientMessageDeduplicator {</tspan></text>
<text x="24" y="3994" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:20 </tspan></text>
<text x="24" y="4016" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:21 private final ConcurrentMap&lt;String, GrpcClientStreamCheckpoint&gt; checkpoints =</tspan></text>
<text x="24" y="4038" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:22 new ConcurrentHashMap&lt;&gt;();</tspan></text>
<text x="24" y="4060" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:23 private final ConcurrentMap&lt;String, String&gt; replayableOutcomes = new ConcurrentHashMap</tspan><tspan x="24" dy="22">&lt;&gt;();</tspan></text>
<text x="24" y="4104" 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="4126" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:70 public void recordApplied(</tspan></text>
<text x="24" y="4148" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:71 GrpcClientStreamMessage&lt;?&gt; message, String outcomeReference, Instant at) {</tspan></text>
<text x="24" y="4170" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:72 GrpcClientStreamCheckpoint checkpoint = requireCheckpoint(message.sessionId());</tspan></text>
<text x="24" y="4192" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:73 checkpoints.put(message.sessionId().value(), checkpoint.advancedTo(message.sequence(</tspan><tspan x="24" dy="22">), at));</tspan></text>
<text x="24" y="4236" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:74 if (outcomeReference != null &amp;&amp; !outcomeReference.isBlank()) {</tspan></text>
<text x="24" y="4258" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:75 replayableOutcomes.put(message.dedupKey(), outcomeReference);</tspan></text>
<text x="24" y="4280" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:76 }</tspan></text>
<text x="24" y="4302" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> GrpcClientMessageDeduplicator.java:77 }</tspan></text>
<text x="24" y="4324" 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="4346" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> main · GrpcClientMessageDeduplicator.java:110 public void endSession(GrpcClientStreamSessionId sessionId) {</tspan></text>
<text x="24" y="4368" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> test · GrpcClientMessageDeduplicatorTest.java:117 deduplicator.endSession(session);</tspan></text>
<text x="24" y="4390" 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="4412" 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="4434" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"># 두 클래스가 같은 family 인가</tspan></text>
<text x="24" y="4456" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> modules.json 의 family 키 : 0 건</tspan></text>
<text x="24" y="4478" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc/CLAUDE.md:1 # grpc — local authority for the gRPC platform family</tspan></text>
<text x="24" y="4500" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc/CLAUDE.md:2 </tspan></text>
<text x="24" y="4522" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc/CLAUDE.md:3 이 문서는 `grpc:*` family의 **local authority**다. leaf 목록·gradle path·허용 의존성은</tspan></text>
<text x="24" y="4544" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:3 이 문서는 `grpc-advanced:*` family의 **local authority**다. leaf 목록·gradle path·허용 의존성</tspan><tspan x="24" dy="22"></tspan></text>
<text x="24" y="4588" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:4 `src/config/architecture/modules.json`이 SSOT다. Root 정책(`CLAUDE.md` / `AGENTS.md`)과 충돌하면</tspan></text>
<text x="24" y="4610" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:5 root가 이긴다.</tspan></text>
<text x="24" y="4632" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:6 </tspan></text>
<text x="24" y="4654" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:7 이 family는 Stable gRPC 플랫폼(`grpc:*`)이 **의도적으로 제외한** 능력들을 담는다. 별도 디렉터리</tspan><tspan x="24" dy="22"></tspan></text>
<text x="24" y="4698" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:8 별도 Gradle prefix인 이유는 하나다: &quot;Stable starter가 advanced module을 참조하면 build가 실패한</tspan><tspan x="24" dy="22">&quot;</tspan></text>
<text x="24" y="4742" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:9 불변 조건을 registry의 `allowed_dependencies`만으로 기계 검증할 수 있게 하기 위해서다.</tspan></text>
<text x="24" y="4764" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:15 grpc:* → grpc-advanced:* (금지 — registry가 거부한다)</tspan></text>
<text x="24" y="4786" fill="#e6edf3" font-size="14" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" xml:space="preserve"><tspan x="24" dy="0"> src/grpc-advanced/CLAUDE.md:74 - Stable leaf(`grpc:*`)가 이 family를 참조하는 것.</tspan></text>
</svg>