docs(keycloak-session-store): take the sentences out of the diagram canvases

The skill says drawings carry names and sentences go in <desc> and the
paragraph beside the figure. I put sentences in node details and edge labels
instead, and 27 of the 28 diagrams shipped with prose inside the canvas —
"예측 다섯 개가 틀렸다", "아홉 번 조용히 실패했다", "막혀서 닿지 않는다".

Only label and details render on the canvas; description does not. So every
sentence moved to a noun phrase and the meaning stays in description, which
was already carrying it.

  막혀서 닿지 않는다              -> 차단
  아홉 번 조용히 실패했다         -> 조용한 실패 9건
  예측 다섯 개가 틀렸다           -> 틀린 예측 5건
  로그아웃이 정리하지 않는다      -> 로그아웃 미정리
  볼륨이 없으면 여기까지다        -> 볼륨 없음

Three node labels were sentences too and became names: 세션 스냅샷, 예측
선기록, 대조군 확보.

What stays is what the rules protect — identifiers, commands and measured
values: PRIMARY KEY (client_registration_id, principal_name),
ValidationFailedException: 1 changesets check sum, nginx -t && nginx -s
reload, SET LOCAL synchronous_commit TO OFF. Those are names of things, not
prose about them.

294 canvas strings across 28 diagrams, none matching a sentence ending,
average 11 characters. All 28 still lint clean and re-rendered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-05 11:50:33 +09:00
co-authored by Claude Opus 5
parent 75bed382c8
commit 95c0e680b5
221 changed files with 1156 additions and 1152 deletions
@@ -54,7 +54,7 @@
"emphasis": "warning",
"description": "무효화 통지가 지나는 길. NetworkPolicy 로 막았다.",
"details": [
"8080·9000 만 열고 누락시킨다"
"허용 포트 8080 · 9000"
],
"evidence": [
{
@@ -72,7 +72,7 @@
"emphasis": "primary",
"description": "통지를 못 받아 세션이 죽은 것을 모른다.",
"details": [
"400 이어야 할 refresh 가 200"
"refresh 200 (기대 400)"
],
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "t-k1",
"from": "transport",
"to": "k1",
"label": "막혀서 닿지 않는다",
"label": "차단",
"kind": "blocked",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "k1-db",
"from": "k1",
"to": "db",
"label": "세션은 여기서 읽는다 — 7800 과 무관",
"label": "세션 SELECT",
"kind": "read",
"evidence": [
{
@@ -38,7 +38,7 @@
"emphasis": "primary",
"description": "200 과 토큰을 받는다.",
"details": [
"로그인 153건 성공"
"성공 응답 153건"
],
"evidence": [
{
@@ -92,7 +92,7 @@
"emphasis": "primary",
"description": "여기 닿아야 살아남는다.",
"details": [
"DB 에 실제로 존재 149건"
"DB 존재 149건"
],
"evidence": [
{
@@ -54,7 +54,7 @@
"emphasis": "warning",
"description": "traefik 과 API 서버. 잃으면 000 이다.",
"details": [
"kubectl 불통"
"kubectl 불통"
],
"evidence": [
{
@@ -72,7 +72,7 @@
"emphasis": "normal",
"description": "4b 에서는 계속 돌고 있었다.",
"details": [
"도달할 수 없을 뿐이다"
"도달 불가"
],
"evidence": [
{
@@ -90,7 +90,7 @@
"emphasis": "warning",
"description": "4a 에서 함께 죽는다. 잃으면 503 이다.",
"details": [
"local-path PVC 재배치 불가"
"local-path PVC · 재배치 불가"
],
"evidence": [
{
@@ -92,7 +92,7 @@
"emphasis": "normal",
"description": "비대칭 차단에서는 여기로 재연결한다.",
"details": [
"가르지 못한다"
"재연결 경로"
],
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "f-k1",
"from": "fwd",
"to": "k1",
"label": "막혀서 닿지 않는다",
"label": "차단",
"kind": "blocked",
"evidence": [
{
@@ -108,7 +108,7 @@
"id": "i-r",
"from": "inject",
"to": "roundtrips",
"label": "왕복마다 더해진다",
"label": "왕복당 누적",
"kind": "request",
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "r-p",
"from": "roundtrips",
"to": "pool",
"label": "커넥션 점유가 길어진다",
"label": "커넥션 점유",
"kind": "request",
"evidence": [
{
@@ -108,7 +108,7 @@
"id": "r-c",
"from": "restart",
"to": "cache",
"label": "비운다",
"label": "캐시 초기화",
"kind": "write",
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "r-db",
"from": "restart",
"to": "db",
"label": "건드리지 않는다",
"label": "변경 없음",
"kind": "read",
"evidence": [
{
@@ -136,7 +136,7 @@
"id": "db-rt",
"from": "db",
"to": "refresh",
"label": "세션이 남아 있어 통한다",
"label": "세션 조회",
"kind": "read",
"evidence": [
{
@@ -104,7 +104,7 @@
"id": "s1-t",
"from": "s1",
"to": "token-row",
"label": "principal 이름으로 쓴다",
"label": "principal 이름",
"kind": "write",
"evidence": [
{
@@ -118,7 +118,7 @@
"id": "s2-t",
"from": "s2",
"to": "token-row",
"label": "같은 키 덮어쓴다",
"label": "같은 키 · 덮어쓰기",
"kind": "write",
"evidence": [
{
@@ -132,7 +132,7 @@
"id": "t-l",
"from": "token-row",
"to": "leftover",
"label": "로그아웃이 정리하지 않는다",
"label": "로그아웃 미정리",
"kind": "blocked",
"evidence": [
{
@@ -38,7 +38,7 @@
"emphasis": "primary",
"description": "같은 refresh token 을 쓴다.",
"details": [
"& wait 이 없으면 재현되지 않는다"
"& + wait 필요"
],
"evidence": [
{
@@ -132,7 +132,7 @@
"id": "cs-nt",
"from": "client-session",
"to": "new-token",
"label": "세션이 없으니 못 쓴다",
"label": "사용 불가",
"kind": "blocked",
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "warning",
"description": "설정하지 않은 이름은 덮어쓰지 않는다.",
"details": [
"proxy_set_header ... \"\" 로 먼저 지워야 한다"
"proxy_set_header ... \"\""
],
"evidence": [
{
@@ -92,7 +92,7 @@
"emphasis": "primary",
"description": "헤더를 믿고 인가한다.",
"details": [
"세션은 로그인 시점 스냅샷"
"로그인 시점 스냅샷"
],
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "n-p",
"from": "nginx",
"to": "proxy",
"label": "지우지 않으면 그대로 지난다",
"label": "미삭제 시 통과",
"kind": "request",
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "warning",
"description": "컨테이너 파일시스템이다.",
"details": [
"appendonlydir 이 만들어졌다 버려진다"
"appendonlydir"
],
"evidence": [
{
@@ -74,7 +74,7 @@
"emphasis": "warning",
"description": "죽으면 /data 도 같이 사라진다.",
"details": [
"볼륨으면 여기까지다"
"볼륨 없"
],
"evidence": [
{
@@ -106,7 +106,7 @@
"id": "c-d",
"from": "config",
"to": "datadir",
"label": "AOF 파일을 쓴다",
"label": "AOF 기록",
"kind": "write",
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "d-c",
"from": "datadir",
"to": "container",
"label": "컨테이너와 함께 죽는다",
"label": "컨테이너와 함께 소멸",
"kind": "blocked",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "d-v",
"from": "datadir",
"to": "volume",
"label": "볼륨을 붙여야 남는다",
"label": "볼륨 마운트",
"kind": "write",
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "warning",
"description": "세션 id 와 복호화 키가 함께 암호화되어 있다.",
"details": [
"secret 을 바꾸면 못 푼다"
"secret 교체 시 복호화 불가"
],
"evidence": [
{
@@ -74,7 +74,7 @@
"emphasis": "warning",
"description": "티켓을 못 풀면 어느 키인지도 모른다.",
"details": [
"고아로 남는다"
"고아 세션"
],
"evidence": [
{
@@ -92,7 +92,7 @@
"emphasis": "primary",
"description": "요청으로 갱신되지 않아 생성 시각의 함수다.",
"details": [
"역산으로 고아를 고른다"
"생성 시각 역산"
],
"evidence": [
{
@@ -108,7 +108,7 @@
"id": "s-t",
"from": "secret",
"to": "ticket",
"label": "티켓 암호화한다",
"label": "티켓 암호화",
"kind": "write",
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "t-r",
"from": "ticket",
"to": "redis-session",
"label": "키 이름을 만든다",
"label": "키 이름 생성",
"kind": "read",
"evidence": [
{
@@ -136,7 +136,7 @@
"id": "r-ttl",
"from": "redis-session",
"to": "ttl",
"label": "생성 시각이 여기 남는다",
"label": "생성 시각",
"kind": "read",
"evidence": [
{
@@ -54,7 +54,7 @@
"emphasis": "warning",
"description": "클라이언트 설정. 비어 있었다.",
"details": [
"Keycloak 은 부를 주소를 모른다"
"주소 없음"
],
"evidence": [
{
@@ -90,7 +90,7 @@
"emphasis": "warning",
"description": "그대로 남는다.",
"details": [
"네트워크 200 으로 닿는다"
"네트워크 도달 200"
],
"evidence": [
{
@@ -106,7 +106,7 @@
"id": "l-c",
"from": "logout",
"to": "kc-config",
"label": "통지 주소를 찾는다",
"label": "통지 주소 조회",
"kind": "request",
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "c-e",
"from": "kc-config",
"to": "app-endpoint",
"label": "주소가 있어야 부른다",
"label": "호출 (주소 필요)",
"kind": "blocked",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "e-s",
"from": "app-endpoint",
"to": "app-session",
"label": "받을 곳이 있어야 지운다",
"label": "세션 삭제 (엔드포인트 필요)",
"kind": "blocked",
"evidence": [
{
@@ -72,7 +72,7 @@
"emphasis": "warning",
"description": "refresh 만 쏘고 첫 번째만 쏜다.",
"details": [
"DEFAULT_SCOPE='f' — 선택적 스코프"
"DEFAULT_SCOPE='f'"
],
"evidence": [
{
@@ -90,7 +90,7 @@
"emphasis": "primary",
"description": "세운 상태다. 여기 닿는 조회만 실패한다.",
"details": [
"캐시에 있으면 닿지 않는다"
"캐시 미스 시에만 도달"
],
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "c-s",
"from": "client-lookup",
"to": "scope-lookup",
"label": "refresh 는 스코프도 다시 계산한다",
"label": "스코프 재계산",
"kind": "read",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "s-db",
"from": "scope-lookup",
"to": "db",
"label": "캐시에 없으면 여기까지 간다",
"label": "캐시 미스",
"kind": "read",
"evidence": [
{
@@ -72,7 +72,7 @@
"emphasis": "primary",
"description": "첫 파드가 실패하면 거기서 멈춘다.",
"details": [
"나머지 파드는 건드리지 않는다"
"나머지 파드 유지"
],
"evidence": [
{
@@ -106,7 +106,7 @@
"emphasis": "primary",
"description": "전후로 같으면 롤백된다.",
"details": [
"늘었으면 스키마가 움직였다"
"전후 행 수 비교"
],
"evidence": [
{
@@ -136,7 +136,7 @@
"id": "l-ro",
"from": "liquibase",
"to": "rolling",
"label": "실패가 여기서 멈춘다",
"label": "중단 지점",
"kind": "blocked",
"evidence": [
{
@@ -150,7 +150,7 @@
"id": "ro-s",
"from": "rolling",
"to": "service",
"label": "남은 파드 응답한다",
"label": "잔여 파드 응답",
"kind": "request",
"evidence": [
{
@@ -164,7 +164,7 @@
"id": "l-cl",
"from": "liquibase",
"to": "changelog",
"label": "무엇을 보고 판단하나",
"label": "판단 기준",
"kind": "read",
"evidence": [
{
@@ -38,7 +38,7 @@
"emphasis": "warning",
"description": "base64 로 담긴다.",
"details": [
"인코딩이지 암호화가 아니다"
"base64 인코딩"
],
"evidence": [
{
@@ -88,7 +88,7 @@
"id": "s-e",
"from": "secret",
"to": "etcd",
"label": "여기 저장된다",
"label": "저장",
"kind": "write",
"evidence": [
{
@@ -102,7 +102,7 @@
"id": "s-p",
"from": "secret",
"to": "pod",
"label": "주입된다",
"label": "주입",
"kind": "write",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "w-s",
"from": "worker",
"to": "serving",
"label": "새 워커가 새 인증서를 읽는다",
"label": "새 인증서 적재",
"kind": "request",
"evidence": [
{
@@ -38,7 +38,7 @@
"emphasis": "primary",
"description": "NetworkPolicy · tc · kill · iptables.",
"details": [
"아홉 번 조용 실패했다"
"조용 실패 9건"
],
"evidence": [
{
@@ -74,7 +74,7 @@
"emphasis": "primary",
"description": "여기만 보면 두 상황이 같아 보인다.",
"details": [
"주입 실패 = 영향 없음 으로 읽힌다"
"실패 · 무영향 구별 불가"
],
"evidence": [
{
@@ -90,7 +90,7 @@
"id": "i-v",
"from": "inject",
"to": "verify",
"label": "걸렸는가를 먼저 본다",
"label": "상태 확인",
"kind": "request",
"evidence": [
{
@@ -104,7 +104,7 @@
"id": "v-o",
"from": "verify",
"to": "observe",
"label": "걸린 뒤에만 결과를 읽는다",
"label": "결과 판독",
"kind": "request",
"evidence": [
{
@@ -72,7 +72,7 @@
"emphasis": "primary",
"description": "대조군과 대비될 때만 성립한다.",
"details": [
"대조군 0 이므로 한 번도 갱신 탓"
"대조군 0 → 귀속 성립"
],
"evidence": [
{
@@ -90,7 +90,7 @@
"emphasis": "warning",
"description": "대조군이 없으면 여기로 간다.",
"details": [
"76건 실패는 로컬 아티팩트였다"
"로컬 아티팩트 76건"
],
"evidence": [
{
@@ -106,7 +106,7 @@
"id": "b-o",
"from": "baseline",
"to": "observation",
"label": "비교 기준을 준다",
"label": "비교 기준",
"kind": "read",
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "o-a",
"from": "observation",
"to": "attribution",
"label": "대조으면",
"label": "대조",
"kind": "request",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "o-m",
"from": "observation",
"to": "misreport",
"label": "대조으면",
"label": "대조",
"kind": "blocked",
"evidence": [
{
@@ -38,7 +38,7 @@
"emphasis": "warning",
"description": "26 미만이면 세션의 거처가 다르다.",
"details": [
"A층 결론 중 셋이 뒤집힌다"
"A층 결론 3건 반전"
],
"evidence": [
{
@@ -106,7 +106,7 @@
"id": "v-c",
"from": "version",
"to": "cache",
"label": "버전이 먼저다",
"label": "선행 조건",
"kind": "request",
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "c-i",
"from": "cache",
"to": "instances",
"label": "다음",
"label": "다음 조건",
"kind": "request",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "i-cl",
"from": "instances",
"to": "clock",
"label": "수치를 읽을 때",
"label": "수치 해석",
"kind": "request",
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "normal",
"description": "사용자가 겪는 것.",
"details": [
"분단도 200 이 나올 수 있다"
"분단 시에도 200"
],
"evidence": [
{
@@ -74,7 +74,7 @@
"emphasis": "warning",
"description": "덮어쓰기와 정리 누락은 여기가 원인이다.",
"details": [
"세션 id 가 키에 없다"
"키에 세션 id 없음"
],
"evidence": [
{
@@ -86,7 +86,7 @@
},
{
"id": "snapshot",
"label": "세션 스냅샷이다",
"label": "세션 스냅샷",
"kind": "process",
"role": "target",
"emphasis": "warning",
@@ -108,7 +108,7 @@
"id": "q-s",
"from": "questions",
"to": "store-choice",
"label": "먼저 여기를 본다",
"label": "1차 검토",
"kind": "request",
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "s-sc",
"from": "store-choice",
"to": "schema",
"label": "여기서 풀리지 않는 것",
"label": "미해결 잔여",
"kind": "request",
"evidence": [
{
@@ -136,7 +136,7 @@
"id": "sc-sn",
"from": "schema",
"to": "snapshot",
"label": "남는 것",
"label": "최종 잔여",
"kind": "request",
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "primary",
"description": "심볼릭 링크. 경로는 그대로이고 가리키는 대상만 바뀐다.",
"details": [
"archive/cert2.pem 을 가리킨다"
"archive/cert2.pem"
],
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "needs-reload",
"from": "live-link",
"to": "nginx",
"label": "reload 없이는 닿지 않는다",
"label": "reload 필요",
"kind": "blocked",
"evidence": [
{
@@ -32,13 +32,13 @@
"nodes": [
{
"id": "prose",
"label": "산문으로 적힌 측정 장치",
"label": "산문 상태의 측정 장치",
"kind": "component",
"role": "source",
"emphasis": "warning",
"description": "괄호와 설명으로 남아 있었다.",
"details": [
"( curl ... ) & 를 20개 띄우고 wait"
"부하 생성기 20건"
],
"evidence": [
{
@@ -90,7 +90,7 @@
"emphasis": "primary",
"description": "실행까지 확인된 것만.",
"details": [
"상주 탐침으로 20/20"
"상주 탐침 20/20"
],
"evidence": [
{
@@ -106,7 +106,7 @@
"id": "p-c",
"from": "prose",
"to": "command",
"label": "바꿔 적는다",
"label": "명령화",
"kind": "request",
"evidence": [
{
@@ -120,7 +120,7 @@
"id": "c-e",
"from": "command",
"to": "execute",
"label": "돌려본다",
"label": "실행",
"kind": "request",
"evidence": [
{
@@ -134,7 +134,7 @@
"id": "e-v",
"from": "execute",
"to": "verified",
"label": "통과한 것만 남긴다",
"label": "통과분 채택",
"kind": "request",
"evidence": [
{
@@ -38,7 +38,7 @@
"emphasis": "primary",
"description": "로그인을 처리하고 자기 sessions 캐시에만 엔트리를 남긴다.",
"details": [
"자기 처리한 로그인만 캐시"
"자기 처리만 캐시"
],
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "primary",
"description": "OFFLINE_USER_SESSION 에 세션 행을 보관한다. 두 노드가 같은 행을 본다.",
"details": [
"offline_flag='0' 이 온라인 세션"
"offline_flag='0'"
],
"evidence": [
{
@@ -74,7 +74,7 @@
"emphasis": "primary",
"description": "다른 노드가 만든 세션을 캐시로 받지 않고 데이터베이스에서 읽는다.",
"details": [
"refresh 요청을 받으면 DB 조회"
"refresh DB 조회"
],
"evidence": [
{
@@ -97,7 +97,8 @@
"end_line": 46
}
],
"assumption": false
"assumption": false,
"details": []
}
],
"edges": [
@@ -38,7 +38,7 @@
"emphasis": "primary",
"description": "Keycloak 26 기본값.",
"details": [
"세션 DB 에 쓴다"
"세션 DB"
],
"evidence": [
{
@@ -56,7 +56,7 @@
"emphasis": "warning",
"description": "24 이전 방식.",
"details": [
"세션 메모리에만 있다"
"세션 메모리"
],
"evidence": [
{
@@ -108,7 +108,7 @@
"id": "p-to-db",
"from": "persistent",
"to": "db-session",
"label": "세션을 여기 쓴다",
"label": "세션 기록",
"kind": "write",
"evidence": [
{
@@ -122,7 +122,7 @@
"id": "v-to-mem",
"from": "volatile",
"to": "memory-session",
"label": "세션이 여기 있다",
"label": "세션 보관",
"kind": "write",
"evidence": [
{
@@ -32,13 +32,13 @@
"nodes": [
{
"id": "predict",
"label": "예측을 먼저 적는다",
"label": "예측 선기록",
"kind": "process",
"role": "source",
"emphasis": "primary",
"description": "결과를 보고 나면 정직하게 쓸 수 없다.",
"details": [
"예측 다섯 개가 틀렸다"
"틀린 예측 5건"
],
"evidence": [
{
@@ -50,13 +50,13 @@
},
{
"id": "verify",
"label": "주입이 걸렸는지 확인",
"label": "주입 검증",
"kind": "process",
"role": "control",
"emphasis": "primary",
"description": "아무 일도 없었다와 영향이 없다를 가른다.",
"details": [
"아홉 번 조용 실패했다"
"조용 실패 9건"
],
"evidence": [
{
@@ -68,7 +68,7 @@
},
{
"id": "control",
"label": "대조군을 잡는다",
"label": "대조군 확보",
"kind": "process",
"role": "control",
"emphasis": "warning",