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:
co-authored by
Claude Opus 5
parent
43bccd08a8
commit
b2963105a8
+103
@@ -0,0 +1,103 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: the-same-rotation-defect-closed-once-and-reproduced
|
||||
title: 같은 자격 증명 회전 결함이 한 가족에서 닫히고 다른 가족에서 재현됐다
|
||||
topic: learning-transfer-between-families
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:the-same-rotation-defect-closed-once-and-reproduced
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
body: case-the-same-rotation-defect-closed-once-and-reproduced.body.md
|
||||
assets:
|
||||
- key: the-same-rotation-defect-closed-once-and-reproduced
|
||||
file: ../../../final/evidence/rendered/the-same-rotation-defect-closed-once-and-reproduced.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/the-same-rotation-defect-closed-once-and-reproduced.txt
|
||||
- ../../../final/evidence/raw/tl-rotation-defect-reproduced.txt
|
||||
source:
|
||||
- 원본 분석 절은 final/document.md#5-5 · analysis/19 §5.4 · analysis/20 §7.4 이다.
|
||||
---
|
||||
|
||||
# 같은 자격 증명 회전 결함이 한 가족에서 닫히고 다른 가족에서 재현됐다
|
||||
|
||||
messaging 가족이 동기화 없는 읽기-갱신 회전 결함을 닫고 그 이력을 계약 테스트에 남겼다. gRPC 가족의 회전 매니저는 AtomicReference 를 평범한 홀더로 쓰며 같은 형태를 반복하고, 그 javadoc 은 경합의 존재를 이미 알고 있다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **Atomic 타입의 존재는 원자성의 증거가 아니다**
|
||||
이 사례가 그 규칙의 대표 형태다.
|
||||
- **레지스트리로 표현된 규칙은 전이되고 CI로 표현된 규칙은 전이되지 않는다**
|
||||
같은 저장소 안에서 학습이 전이되지 않은 사례다.
|
||||
- **두 번째 플랫폼이 첫 번째의 bridge 부재는 막고 게이트 배선은 옮기지 않았다**
|
||||
같은 두 가족 사이의 전이 패턴을 다룬다.
|
||||
|
||||
## 문제
|
||||
|
||||
messaging 가족의 자격 증명 런타임 레지스트리는 예전에 동기화 없이 읽고 가져오고 넣고 지웠다.
|
||||
|
||||
그 결함을 계약 테스트의 javadoc 이 사후 기록으로 남긴다. 같은 자격 증명을 회전시키는 두 호출자가 둘 다 같은 옛 런타임을 읽고 둘 다 교체본을 가져왔다. 하나의 교체본이 한 번도 정리되지 않은 채 맵에서 버려졌다. 아무도 소유하지 않는 비밀이 메모리에 남은 것이다. 그리고 진 쪽이 이긴 쪽이 아직 쓰고 있는 자료를 지울 수 있었다.
|
||||
|
||||
결함은 닫혔고 이력은 남았다.
|
||||
|
||||
## 결론
|
||||
|
||||
gRPC 가족의 회전 매니저가 같은 형태를 반복한다.
|
||||
|
||||
그 클래스는 AtomicReference 를 필드로 갖는다. 그러나 원자적 갱신 연산을 한 번도 쓰지 않는다.
|
||||
|
||||
get 으로 읽고 set 으로 쓴다
|
||||
compareAndSet 사용 0
|
||||
updateAndGet 사용 0
|
||||
synchronized 사용 0
|
||||
|
||||
읽은 값으로 다음 상태를 만들어 쓰는 지점이 여럿이다. 상태를 관측하고 그 관측값으로 새 상태를 구성해 설정하는 형태다. 두 회전자가 동시에 들어오면 나중 쓰기가 앞선 쓰기를 덮는다.
|
||||
|
||||
그리고 같은 파일의 javadoc 이 그 경합을 이미 이름으로 부른다. 회전자가 둘 겹치는 것이 그 상황의 통상적인 이유라고 적혀 있다.
|
||||
|
||||
정책 리프의 테스트 열여섯 개 중 동시성을 다루는 것이 없다.
|
||||
|
||||
두 가족이 같은 저장소 안에 있고, 앞선 결함의 이력이 코드 주석으로 남아 있는데도 전이되지 않았다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
확인 방식 : 두 구현의 코드 비교와 연산 계수
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 final/evidence/raw/tl-rotation-defect-reproduced.txt 에 있다.
|
||||
|
||||
1. messaging 자격 증명 계약 테스트의 javadoc 에서 닫힌 결함의 서술을 읽는다.
|
||||
2. gRPC 회전 매니저에서 AtomicReference 의 사용 지점을 열거한다.
|
||||
3. 같은 파일에서 compareAndSet 과 updateAndGet 과 synchronized 를 센다. 셋 다 0 이다.
|
||||
4. 그 파일의 javadoc 에서 경합을 언급하는 문장을 찾는다.
|
||||
5. 정책 리프의 테스트 중 동시성을 다루는 것을 센다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
messaging이 get → fetch → put → clear를 동기화 없이 하던 결함을 닫고 이력을 계약 테스트 javadoc에 남겼다 — "one replacement was dropped from the map without ever being cleared — a secret left in memory that nothing owns."
|
||||
|
||||
## messaging 이 닫고 남긴 이력
|
||||
|
||||
:::evidence key="the-same-rotation-defect-closed-once-and-reproduced" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
|
||||
:::
|
||||
|
||||
## gRPC 쪽은 CAS 를 한 번도 쓰지 않는다
|
||||
|
||||
회전 매니저는 `AtomicReference`를 쓰면서 `compareAndSet`을 한 번도 쓰지 않고 `get()`→`set()`으로만 다룬다(`synchronized`도 0). javadoc이 그 경합의 존재를 이미 알고 있다 — "the usual reason for one is two rotators racing".
|
||||
|
||||
## 테스트 열여섯 중 동시성을 다루는 것이 없다
|
||||
|
||||
`grpc-policy` 쪽이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
경합을 재현하는 동시성 테스트를 작성하지 않았다. 이 기록은 코드 형태와 앞선 사례의 대조에 근거한다.
|
||||
|
||||
gRPC 블록은 어떤 배포에도 포함되지 않으므로 이 경합이 프로덕션에서 일어날 수 있는 상태는 아니다.
|
||||
|
||||
<!-- body:end -->
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
---
|
||||
kind: CASE
|
||||
slug: the-second-platform-carried-the-design-not-the-wiring
|
||||
title: 두 번째 플랫폼이 첫 번째의 bridge 부재는 막고 게이트 배선은 옮기지 않았다
|
||||
topic: learning-transfer-between-families
|
||||
project: clean-architecture-backend-template
|
||||
status: 게시 전
|
||||
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
|
||||
rootTreeNode: case:the-second-platform-carried-the-design-not-the-wiring
|
||||
evidenceCapturedOn: 2026-09-01
|
||||
assets:
|
||||
- key: the-second-platform-carried-the-design-not-the-wiring
|
||||
file: ../../../final/evidence/rendered/the-second-platform-carried-the-design-not-the-wiring.svg
|
||||
evidence:
|
||||
- ../../../final/evidence/raw/the-second-platform-carried-the-design-not-the-wiring.txt
|
||||
- ../../../final/evidence/raw/tl-platform-suite-results.txt
|
||||
- ../../../final/evidence/raw/tl-grpc-evidence-lanes.txt
|
||||
source:
|
||||
- 원본 분석 절은 final/document.md#7-6 · analysis/20 §2, §3 · analysis/99-cross-scope.md §2, §4 이다.
|
||||
---
|
||||
|
||||
# 두 번째 플랫폼이 첫 번째의 bridge 부재는 막고 게이트 배선은 옮기지 않았다
|
||||
|
||||
gRPC 가족은 messaging 을 명시적으로 참조하며 만들어졌다. 옮겨진 것 셋은 전부 레지스트리와 빌드 파일로 표현되는 규칙이고, 옮겨지지 않은 것 셋은 전부 Gradle 태스크와 CI 설정으로 표현되는 규칙이다.
|
||||
|
||||
## 관계
|
||||
|
||||
- **레지스트리로 표현된 규칙은 전이되고 CI로 표현된 규칙은 전이되지 않는다**
|
||||
이 사례에서 끌어낸 규칙이다.
|
||||
- **릴리스 게이트가 읽는 증거를 아무도 생산하지 않는다**
|
||||
옮겨지지 않은 것 중 하나의 구체적 결과다.
|
||||
- **gRPC 플랫폼은 build-only로 두고 애플리케이션 도달 경로를 먼저 정한다**
|
||||
옮겨진 것 중 가장 무거운 판단이다.
|
||||
- **같은 자격 증명 회전 결함이 한 가족에서 닫히고 다른 가족에서 재현됐다**
|
||||
같은 두 가족 사이에서 전이되지 않은 다른 사례다.
|
||||
|
||||
## 문제
|
||||
|
||||
messaging 가족은 스타터를 컴포지션 루트의 의존으로 넣으면서 25개 리프가 출하 아티팩트에 실렸다. 그 결과 배선되지 않은 코드가 배포에 포함되는 문제가 실재하게 됐다.
|
||||
|
||||
gRPC 가족은 그 이후에 만들어졌고 messaging 을 명시적으로 참조한다. 목표도 문서에 적혀 있다.
|
||||
|
||||
그렇다면 무엇이 옮겨졌고 무엇이 옮겨지지 않았는가.
|
||||
|
||||
## 결론
|
||||
|
||||
옮겨진 것 셋은 전부 레지스트리와 빌드 파일로 표현된다.
|
||||
|
||||
다리를 먼저 정하기로 한 것. 18개 리프 전부의 런타임 멤버십이 비어 있고, 인바운드 어댑터의 허용 의존에 이 가족이 없다. 레지스트리가 그것을 강제한다.
|
||||
프레임워크 없는 계약 리프를 두는 것. 빌드 파일의 테스트 클래스패스 조건이 그것을 검증 가능하게 만든다.
|
||||
속성 기반 전송 선택. 클래스패스 조건이 아니라 닫힌 레지스트리에서 이름으로 고른다.
|
||||
|
||||
옮겨지지 않은 것 셋은 전부 Gradle 태스크와 CI 설정으로 표현된다.
|
||||
|
||||
증거에서 파생되는 등급. gRPC 쪽에는 증거를 생산하는 태스크가 없다.
|
||||
CI 가 돌리는 게이트. 28개 워크플로 중 gRPC 를 이름에 담은 것이 0 이다.
|
||||
조립에 연결된 시작 검증기. 검증기는 있고 자동설정이 부르지 않는다.
|
||||
|
||||
분류가 깨끗하다. 옮겨진 셋은 전부 다음 사람이 편집하게 되는 파일에 있고, 옮겨지지 않은 셋은 전부 그렇지 않은 파일에 있다.
|
||||
|
||||
운영 문서는 그 상태를 정확히 공시한다. 출시되지 않았고 빌드 전용이라고 적는다. 이 점에서 messaging 의 지원 매트릭스와 대비된다.
|
||||
|
||||
## 검증 환경
|
||||
|
||||
OpenJDK : 21.0.12
|
||||
Gradle : 9.0.0
|
||||
확인 방식 : 레지스트리 항목 계수, Gradle 태스크와 CI 워크플로 계수, 문서 대조
|
||||
소스 수정 : x
|
||||
|
||||
## 재현 조건
|
||||
|
||||
원문은 final/evidence/raw 아래 tl-platform-suite-results.txt 와 266-grpc-family-claims-verification.txt 와 250-messaging-certification-gate-chain.txt 에 있다.
|
||||
|
||||
1. 레지스트리에서 gRPC 리프들의 런타임 멤버십을 확인한다. 전부 비어 있다.
|
||||
2. 인바운드 gRPC 어댑터의 허용 의존에 이 가족이 있는지 확인한다.
|
||||
3. src/grpc 아래 등록된 Gradle 태스크 수를 센다.
|
||||
4. CI 워크플로 중 gRPC 를 이름에 담은 것을 센다.
|
||||
5. messaging 쪽 같은 사슬을 세어 대조한다.
|
||||
6. 블록의 CLAUDE.md 와 호환성 문서의 상태 표기를 읽는다.
|
||||
|
||||
## 본문
|
||||
|
||||
<!-- body:start -->
|
||||
|
||||
gRPC 가족이 messaging을 명시적으로 참조하며 만들어졌고 목표를 문서에 적었다.
|
||||
|
||||
## gRPC 가족이 문서에 적은 목표
|
||||
|
||||
:::evidence key="the-second-platform-carried-the-design-not-the-wiring" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
|
||||
:::
|
||||
|
||||
## 옮겨진 셋과 옮겨지지 않은 셋이 표현 형식으로 갈린다
|
||||
|
||||
옮겨진 셋(bridge를 먼저 정함·framework-free 계약 leaf·속성 기반 전송 선택)은 전부 레지스트리와 `build.gradle`로 표현되는 규칙이고, 옮겨지지 않은 셋(증거 파생 등급·CI가 돌리는 게이트·조립에 연결된 시작 검증기)은 전부 Gradle 태스크와 CI YAML로 표현되는 규칙이다.
|
||||
|
||||
## 확인하지 못한 것
|
||||
|
||||
이 분류가 인과인지 상관인지 확인할 수 없다. 작성자가 왜 후자를 옮기지 않았는지에 대한 기록이 없다. 표본도 두 가족뿐이다.
|
||||
|
||||
<!-- body:end -->
|
||||
Reference in New Issue
Block a user