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,58 @@
---
kind: PROJECT_DECISION
slug: capability-separates-installation-from-activation
title: capability는 스키마 적용과 사용 승인을 분리한다
topic: owner-safe-state-machines
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: decision:capability-separates-installation-from-activation
decisionStatus: ADOPTED
decidedOn: 2026-08-30
source:
- src/adapter/outbound/persistence-jpa/src/main/resources/db/migration/postgresql/V6__capability_schema_registry_adoption.sql
- src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/NotificationSchemaActivation.java
- analysis/05-adapter-outbound-persistence-jpa.md
---
# capability는 스키마 적용과 사용 승인을 분리한다
## 결정문
능력의 스키마가 설치되었다는 사실과 그 능력을 사용해도 된다는 승인을 별개의 기록으로 둔다.
## 판단 이유
두 사실은 서로 다른 주체가 만든다. 설치는 마이그레이션이 돌면 일어나고, 승인은 운영자가 정한다.
하나로 묶으면 마이그레이션 배포가 곧 활성화가 된다. 그러면 단계적 활성화나 롤백 같은 운영 판단이 스키마 배포 일정에 묶인다.
그래서 레지스트리 테이블이 능력별로 스키마 스트림과 설치 출처와 코어 에포크를 기록한다. 설치 출처가 별도 컬럼인 것이 요점이다. 같은 스키마라도 레거시 채택으로 들어온 것과 새 스트림으로 설치된 것이 구별된다.
능력마다 자기 스키마 스트림을 두므로 한 능력의 스키마 변경이 다른 능력의 마이그레이션 번호를 밀지 않는다.
다리 마이그레이션은 자기 전제를 먼저 검사한다. 레거시 채택은 그 레거시가 실제로 있을 때만 의미가 있고, 없는데 진행하면 빈 레지스트리 위에 이후 판단이 전부 선다.
## 영향
감수하는 것
능력을 쓰려면 두 단계를 거쳐야 한다. 스키마만 설치하고 승인을 잊으면 그 능력은 동작하지 않는다.
레지스트리 자체가 관리 대상이 된다. 능력이 늘 때마다 항목이 늘고 그 정합성을 지켜야 한다.
마이그레이션이 전제 검사에서 실패할 수 있다. 그것이 의도이지만 배포 절차가 그 실패를 다룰 줄 알아야 한다.
얻는 것
스키마 배포와 능력 활성화가 독립적이다. 스키마를 먼저 깔아 두고 나중에 켤 수 있다.
설치 출처가 남아 있어 나중에 이 스키마가 어디서 왔는지 물을 수 있다.
## 근거
- **capability_schema_registry — 스키마 적용과 사용 승인의 분리**
이 결정이 만든 구조다.
- **지원 등급은 추론이 아니라 선언이고 증거 없이는 올라가지 않는다**
같은 원칙이 능력 등급에 적용된 결정이다.
@@ -0,0 +1,56 @@
---
kind: PROJECT_DECISION
slug: state-machines-carry-no-stereotype
title: 상태 기계 구현은 Spring stereotype을 갖지 않는다
topic: owner-safe-state-machines
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: decision:state-machines-carry-no-stereotype
decisionStatus: ADOPTED
decidedOn: 2026-08-30
source:
- src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/postgresql/idempotency
- src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/config/JpaAdapterComponentsConfig.java
- analysis/05-adapter-outbound-persistence-jpa.md
---
# 상태 기계 구현은 Spring stereotype을 갖지 않는다
## 결정문
소유자 안전 상태 기계 구현 클래스에는 컴포넌트 스캔 대상이 되는 애너테이션을 붙이지 않고, 조립은 명시적으로 한다.
## 판단 이유
이 클래스들은 어떤 데이터소스와 어떤 트랜잭션 경계에 묶이는지가 정확해야 한다. 스캔으로 들어오면 그 결정이 스캔 범위와 조건에 흩어진다.
같은 리프에서 그 흩어짐이 실제로 문제가 된 사례가 있다. 활성 트랜잭션 검사가 어느 데이터소스인지를 묻지 않아, 다른 데이터소스의 트랜잭션 안에서 발행된 변경이 이 저장소의 커넥션에 대해 트랜잭션 밖에서 커밋됐다.
명시적 조립은 그 결정을 한 자리에 모은다. 어떤 데이터소스가 어떤 저장소에 들어가는지가 코드로 보인다.
그리고 스캔되지 않으면 조건을 반복할 필요도 없다. 능력이 꺼진 배포에서 이 클래스들이 후보가 되는 일 자체가 없다.
## 영향
감수하는 것
조립 코드를 사람이 써야 한다. 새 상태 기계를 추가하면 조립 지점도 함께 고쳐야 한다.
조립을 잊으면 그 상태 기계가 없는 채로 배포된다. 스캔은 그 실수를 자동으로 막아 주지만 명시 조립은 그렇지 않다.
얻는 것
데이터소스와 트랜잭션 경계가 조립 지점에서 명시된다.
능력이 꺼진 배포에서 이 클래스들이 조건 평가 대상이 되지 않는다.
## 근거
- **활성 트랜잭션 검사가 data source를 묻지 않아 다른 커넥션에서 커밋됐다**
조립 결정이 흩어졌을 때의 결과다.
- **꺼짐은 조건의 반복이 아니라 구조여야 한다**
같은 계열의 조립 원칙이다.
- **Bean 애너테이션이 있다는 것은 조립 증거가 아니다**
명시 조립을 확인할 때 쓰는 규칙이다.