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>
This commit is contained in:
DongHyeonka
2026-09-04 22:51:59 +09:00
co-authored by Claude Opus 5
parent 43bccd08a8
commit b2963105a8
5017 changed files with 372751 additions and 4943 deletions
@@ -0,0 +1,56 @@
---
kind: REFERENCE
slug: atomic-type-is-not-atomicity
title: Atomic* 타입의 존재는 원자성의 증거가 아니다
topic: duplicate-mechanisms
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: reference:atomic-type-is-not-atomicity
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# Atomic* 타입의 존재는 원자성의 증거가 아니다
## 목적
Atomic 계열 타입이 쓰였다는 것을 그 연산이 원자적이라는 증거로 읽어, 여러 연산에 걸친 경합을 놓치는 것을 막는다.
## 규칙
1. 원자성의 단위는 타입이 아니라 연산 묶음이다
각각의 읽기와 쓰기가 원자적이어도 둘을 잇는 판단은 원자적이지 않다.
2. 검사 후 행동 형태를 찾는다
값을 읽고 그 값으로 분기해 다시 쓰는 코드는 Atomic 타입을 써도 경합한다. compareAndSet 이나 accumulate 계열이 필요하다.
3. 여러 필드에 걸친 불변식은 Atomic 타입으로 표현되지 않는다
두 값이 함께 바뀌어야 하면 락이나 단일 값으로의 병합이 필요하다.
4. 컬렉션은 원소가 아니라 구조를 본다
원소가 Atomic 이어도 목록 자체의 추가와 순회는 동기화되지 않는다.
5. 단일 스레드 가정이면 그 가정을 적는다
가정이 적혀 있지 않으면 다음 사람이 그 클래스를 다중 스레드 경로에 놓는다.
## 적용 조건
동시 접근이 가능한 모든 상태
인터셉터나 필터처럼 요청마다 호출되는 컴포넌트의 필드
## 예외
단일 카운터의 증감처럼 연산 하나로 끝나는 경우는 Atomic 타입으로 충분하다.
## 예시
동기화 없이 ArrayList 에 추가하고 조회하는 클래스가 있고, 단일 스레드 사용 가정으로 보이지만 그 가정이 서술되어 있지 않다. 그 클래스가 서버 인터셉터 경로에 쓰이면 다중 스레드가 된다.
## 관계
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
같은 계열의 확인 규칙이다.
- **커밋 증거 프레임을 두 주인이 pop해서 바깥 트랜잭션의 실패가 익명이 됐다**
소유권과 동시성이 함께 얽힌 사례다.
@@ -0,0 +1,66 @@
---
kind: REFERENCE
slug: check-which-duplicate-is-wired
title: 중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다
topic: duplicate-mechanisms
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: reference:check-which-duplicate-is-wired
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# 중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다
## 목적
같은 일을 하는 구현이 둘 있을 때 정교한 쪽을 읽고 판정해서, 실제로 도는 약한 쪽을 놓치는 것을 막는다.
## 규칙
1. 조립된 쪽을 먼저 찾는다
어느 쪽이 스테레오타입을 갖는지, 어느 쪽이 빈으로 만들어지는지, 어느 쪽이 설정에서 지목되는지를 본다.
2. 만들어지는 것과 호출되는 것을 나눠 센다
빈이 있다는 것과 그것을 주입받아 부르는 코드가 있다는 것은 다르다. 세 단계로 나눈다. 만들어지는가, 주입되는가, 호출되는가.
3. 약한 쪽이 도는 경우가 흔하다
정교한 구현은 늦게 만들어지고 배선이 뒤따르지 않는 경우가 많다. 그동안 원래 있던 사본이 계속 돈다.
4. 인프라 설정도 중복의 한쪽이다
같은 판정이 애플리케이션 코드와 프록시 설정에 각각 있으면, 도는 것은 대개 프록시 쪽이다.
5. 정본을 정하는 것이 수정이다
둘 다 살려 두면 다음 사람이 어느 쪽을 고쳐야 하는지 모른다.
## 적용 조건
같은 개념의 구현이 둘 이상 발견되는 모든 경우
플랫폼과 애플리케이션이 한 저장소에 함께 있는 경우 특히
## 예외
의도적으로 계층별로 다른 강도를 두는 경우가 있다. 그때는 어느 계층이 무엇을 보장하는지가 문서에 있어야 하고, 약한 쪽이 강한 쪽을 우회하는 경로가 없어야 한다.
## 예시
전체 트랜잭션 재시도 코디네이터는 빈으로 만들어지고 주입받아 호출하는 코드가 0 이다.
웹푸시 구독 값은 클라이언트가 준 엔드포인트를 자기 private 검사로만 확인하고, 플랫폼의 목적지 정책을 지나지 않는다.
forwarded 헤더 신뢰 판정은 421 줄의 Java 정책과 Nginx 설정 양쪽에 있고, 도는 것은 Nginx 쪽이다.
outbox 는 두 스택으로 구현되어 있고 출하되는 것은 application-core 쪽이다.
## 관계
- **재시도 코디네이터는 빈이지만 그것을 어디에도 적용하지 않는다**
두 번째 규칙이 필요한 사례다.
- **클라이언트가 준 엔드포인트가 SSRF 가드가 아니라 약한 private 사본을 지났다**
세 번째 규칙의 사례다.
- **forwarded 헤더 신뢰 판정이 Nginx에 있고 Java 정책 421 LOC은 대부분 참조되지 않는다**
네 번째 규칙의 사례다.
- **조립 결함을 판정하려면 조립하는 쪽을 먼저 읽어야 한다**
같은 계열의 상위 규칙이다.
@@ -0,0 +1,55 @@
---
kind: REFERENCE
slug: two-vocabularies-for-one-concept
title: 같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다
topic: duplicate-mechanisms
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: reference:two-vocabularies-for-one-concept
verifiedOn: # 이 기록은 이번 회차에 실행 확인을 하지 않았다
---
# 같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다
## 목적
같은 개념을 가리키는 이름이 둘 남아 있어, 다음 사람이 어느 쪽이 정본인지 모른 채 둘 다 유지하거나 잘못된 쪽을 고치는 것을 막는다.
## 규칙
1. 정본을 명시한다
두 어휘 중 어느 쪽이 앞으로 쓰일 이름인지 코드나 문서에 적는다.
2. 죽은 쪽에 표시를 남긴다
지울 수 없다면 그것이 유지되지 않는 이름이라는 것과 언제 지울 수 있는지를 적는다.
3. 표시가 없으면 둘 다 살아 있는 것으로 읽힌다
이름이 남아 있는 것 자체가 의도로 읽힌다.
4. 이름이 다른 계층에 걸쳐 있으면 매핑을 한 곳에 둔다
변환이 여러 곳에 흩어지면 그중 하나가 뒤처진다.
## 적용 조건
리팩터링이나 계층 재배치 뒤 남은 옛 이름
같은 도메인 개념을 어댑터와 애플리케이션이 다르게 부르는 경우
## 예외
외부 계약이 옛 이름을 요구하면 그 이름은 죽은 것이 아니라 경계 어휘다. 그 사실이 경계 지점에 적혀 있어야 한다.
## 예시
이 저장소에는 미완성 상태에 이름을 붙이고 기동에서 거절하며 그 이름이 언제 목록에서 빠지는지까지 적은 선례가 있다. 그 항목은 자기 전송이 존재하는 날 이 맵을 떠난다고 적혀 있다.
반대로 구현 계획에만 남은 클래스 이름이 ADR 의 강제 절에 그대로 인용되어, 그 절을 읽으면 존재하지 않는 것이 강제하고 있다고 읽힌다.
## 관계
- **재시도 코디네이터는 빈이지만 그것을 어디에도 적용하지 않는다**
문서에만 남은 이름이 강제 수단으로 인용된 사례다.
- **중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다**
같은 계열의 확인 규칙이다.