Files
document-haness/docs/clean-architecture-backend-template/final/evidence/meta/a19-f003-messaging-reliability-api.json
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

15 lines
6.2 KiB
JSON

{
"assetKey": "a19-f003-messaging-reliability-api",
"kind": "terminal",
"command": "set -e\nset -o pipefail\nM=src/messaging\n\necho \"# sub-scope 01 네 리프의 소스 세트와 계수\"\nfor m in messaging-core-api messaging-transport-spi messaging-reliability-api messaging-runtime-core; do\n d=$M/$m\n mf=$(find $d/src/main -name '*.java' 2>/dev/null | wc -l || true)\n tf=$(find $d/src/test -name '*.java' 2>/dev/null | wc -l || true)\n ml=$(find $d/src/main -name '*.java' 2>/dev/null -exec cat {} + | wc -l || true)\n sets=$(ls $d/src | tr '\\n' ' ')\n printf \" %-26s main=%-3s test=%-3s main줄수=%-5s 소스세트: %s\\n\" \"$m\" \"$mf\" \"$tf\" \"$ml\" \"$sets\"\ndone\n\necho\necho \"# 그 817 줄의 내역\"\npython3 - <<'PY'\nimport pathlib, re\nroot = pathlib.Path('src/messaging/messaging-reliability-api/src/main')\nblank = comment = code = 0\nfor f in sorted(root.rglob('*.java')):\n inblock = False\n for line in f.read_text(encoding='utf-8').splitlines():\n s = line.strip()\n if inblock:\n comment += 1\n if '*/' in s: inblock = False\n elif not s: blank += 1\n elif s.startswith('//'): comment += 1\n elif s.startswith('/*'):\n comment += 1\n if '*/' not in s: inblock = True\n else: code += 1\nprint(f\" 공백 {blank} · 주석 {comment} · 코드 {code} · 합계 {blank+comment+code}\")\nPY\n\necho\necho \"# 열세 타입의 선언 형태와 줄 수\"\nfor f in $(find $M/messaging-reliability-api/src/main -name '*.java' | sort); do\n kind=$(grep -oE 'public (sealed )?(interface|record|enum|final class|abstract class|class)' $f | head -1 || true)\n printf \" %-34s %-14s %s 줄\\n\" \"$(basename $f)\" \"${kind#public }\" \"$(wc -l < $f)\"\ndone\n\necho\necho \"# 실행 가능한 규칙을 가진 유일한 열거형\"\ngrep -n 'isSafeToSettle\\|CLAIMED_ELSEWHERE' $M/messaging-reliability-api/src/main/java/dev/caskeleton/messaging/reliability/InboxResult.java \\\n | sed -E 's#^([0-9]+): *# InboxResult.java:\\1 #' | head -5\necho \" OutboxStatus 와 OutboxTransitionResult 의 메서드 수 :\"\nfor t in OutboxStatus OutboxTransitionResult; do\n n=$(grep -cE '^ (public|private|static).*\\(' $M/messaging-reliability-api/src/main/java/dev/caskeleton/messaging/reliability/$t.java || true)\n printf \" %-24s %s 개\\n\" \"$t\" \"$n\"\ndone\necho \" 전이 규칙이 실제로 선언된 자리 :\"\ngrep -n 'STALE_LEASE\\|still owns' $M/messaging-reliability-api/src/main/java/dev/caskeleton/messaging/reliability/OutboxRepository.java \\\n | head -3 | sed -E 's#^([0-9]+): *# OutboxRepository.java:\\1 #'\n\necho\necho \"# 원문 §3.6 이 지목한 구현 리프 이름과, 같은 문서 §7 이 적은 실제 이름\"\nA=/shared/document-detail/clean-architecture-backend-template/analysis/19-messaging-platform.md\ngrep -n 'messaging-outbox-jdbc\\b\\|messaging-inbox-jdbc\\b' $A | head -2 | cut -c1-120 | sed 's#^# analysis/19:#'\ngrep -n 'messaging-outbox-jdbc-postgresql\\|messaging-inbox-jdbc-postgresql\\|messaging-claim-check' $A | head -3 | cut -c1-120 | sed 's#^# analysis/19:#'\necho \" 저장소에 실재하는 이름 :\"\nfor m in messaging-outbox-jdbc messaging-inbox-jdbc messaging-outbox-jdbc-postgresql messaging-inbox-jdbc-postgresql messaging-claim-check; do\n if [ -d \"$M/$m/src\" ]; then\n printf \" %-34s main=%-3s test=%s\\n\" \"$m\" \"$(find $M/$m/src/main -name '*.java' 2>/dev/null | wc -l || true)\" \"$(find $M/$m/src/test -name '*.java' 2>/dev/null | wc -l || true)\"\n else\n printf \" %-34s src 디렉터리 없음\\n\" \"$m\"\n fi\ndone\necho \" messaging-outbox-jpa · messaging-inbox-jpa 가 등록돼 있는가 :\"\nr=$(grep -c 'messaging-outbox-jpa\\|messaging-inbox-jpa' src/config/architecture/modules.json || true)\ng=$(git ls-files \"$M/messaging-outbox-jpa\" \"$M/messaging-inbox-jpa\" | wc -l || true)\necho \" modules.json 등재 $r 건 · git 추적 파일 $g 개\"\n\necho\necho \"# 두 열거형을 코드에서 참조하는 test (주석·문자열 제거, 단어 경계)\"\npython3 - <<'PY'\nimport pathlib, re, subprocess\nfiles = subprocess.run(['git','grep','-l','Outbox','--','src/**/*.java'],\n capture_output=True, text=True).stdout.split()\ndef strip(src):\n src = re.sub(r'/\\*.*?\\*/', ' ', src, flags=re.S)\n src = re.sub(r'(?m)//.*$', ' ', src)\n return re.sub(r'\"(?:[^\"\\\\]|\\\\.)*\"', ' ', src)\nfor t in ('OutboxStatus', 'OutboxTransitionResult'):\n hits = []\n for p in files:\n if '/test/' not in p: continue\n n = len(re.findall(rf'\\b{t}\\b', strip(pathlib.Path(p).read_text(encoding='utf-8'))))\n if n: hits.append((pathlib.Path(p).name, n, p.split('/src/')[0].split('/')[-1]))\n print(f\" {t} : {len(hits)} 파일\")\n for name, n, leaf in sorted(hits):\n print(f\" {name:<44} {n:>3} 회 ({leaf})\")\nPY\n\necho\necho \"# 열세 타입 중 test 참조가 0 인 것\"\npython3 - <<'PY'\nimport pathlib, re, subprocess\ntypes = sorted(p.stem for p in pathlib.Path('src/messaging/messaging-reliability-api/src/main').rglob('*.java'))\nallj = subprocess.run(['git','grep','-l','','--','src/**/*.java'], capture_output=True, text=True).stdout.split()\ndef strip(src):\n src = re.sub(r'/\\*.*?\\*/', ' ', src, flags=re.S)\n src = re.sub(r'(?m)//.*$', ' ', src)\n return re.sub(r'\"(?:[^\"\\\\]|\\\\.)*\"', ' ', src)\ncache = {p: strip(pathlib.Path(p).read_text(encoding='utf-8', errors='replace')) for p in allj}\nfor t in types:\n tn = sum(1 for p, s in cache.items() if '/test/' in p and re.search(rf'\\b{t}\\b', s))\n mn = sum(1 for p, s in cache.items() if '/main/' in p and f'/{t}.java' not in p and re.search(rf'\\b{t}\\b', s))\n if tn == 0:\n print(f\" {t:<30} test 0 · 자기 파일 밖 main {mn}\")\nPY\n",
"cwd": "/shared/codebase/clean-architecture-backend-template",
"exitCode": 0,
"executedAt": "2026-09-04T00:24:01+00:00",
"sourceRevision": "21234e38cdb9a926cbc92bb97a2aee2e4a7d2916",
"raw": "evidence/raw/a19-f003-messaging-reliability-api.txt",
"svg": "evidence/rendered/a19-f003-messaging-reliability-api.svg",
"rawSha256": "8b996e884b7cf443274749e12409478a387268fa2db06ebe5730d787317547fa",
"lines": 70,
"redaction": "코드베이스 측정으로 교체"
}