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,124 @@
---
kind: CONCEPT
slug: redis-admission-stages
title: 명령 카탈로그와 admission 아홉 단계
topic: redis-command-admission
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: concept:redis-admission-stages
evidenceCapturedOn: 2026-09-01
assets:
- key: redis-admission-stages
file: ../../../final/evidence/rendered/redis-admission-stages.svg
- key: redis-admission-stages-diagram
file: ../../../final/assets/diagrams/redis-admission-stages.svg
evidence:
- ../../../final/evidence/raw/redis-admission-stages.txt
source:
- 원본 분석 절은 final/document.md#4-4 · analysis/10 §64 이다.
---
# 명령 카탈로그와 admission 아홉 단계
모든 Redis 명령이 하나의 승인 지점을 지나고, 그 지점은 정해진 순서로 검사한다. 순서의 기준은 비용이다. 명백히 거부될 명령은 무엇도 인코딩되거나 전송되기 전에 거부된다.
## 관계
- **의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다**
이 단일 지점이 실제로 단일인지에 대한 사례다.
- **단일 admission point는 우회 경로를 세어야 성립한다**
이 개념에서 끌어낸 규칙이다.
- **분류되지 않은 명령은 fail-closed로 거부한다**
카탈로그가 답하지 못하는 명령을 어떻게 다룰지 정한 결정이다.
## 본문
<!-- body:start -->
이 SDK가 명령 하나를 내보내기 전에 지나는 단계의 설명이다 — 카탈로그 분류(BLOCKED·R3·R4 거부) · capability/최소 버전 확인 · permit provenance 검증 · 네임스페이스 검사 · Cluster 동일 슬롯 검사 · 요청 예산 · 정책 기반 레인·타임아웃 유도 · 실패 번역 · 관측.
## 명령 입장의 단일 지점
:::evidence key="redis-admission-stages-diagram" alt="CommandPolicyGuard 에서 카탈로그를 통과하면 실행이고 미분류나 BLOCKED 이면 거절인 두 갈래가 나온다" caption="명령 입장의 단일 지점" zoom="false"
:::
그 위에 얹힌 계약은 gateway가 "everything routed through it has already passed `CommandPolicyGuard`"를 전제한다는 것이다 — 그래서 정책·permit·예산·타임아웃·관측을 자기 관심사로 두지 않는다.
## CommandPolicyGuard 참조 위치
:::evidence key="redis-admission-stages" alt="코드베이스에서 CommandPolicyGuard 를 검색한 출력 26줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="CommandPolicyGuard 코드베이스 검색 — 26줄 · exit 0" zoom="true"
:::
## 이 구조의 바닥
카탈로그가 미분류 명령을 fail-closed로 거부하는 것이다.
:::note
토폴로지 레인 미실행 — 실제 서버에서 각 단계가 도는지 관측하지 않았다
:::
## 순서가 비용순이다
```java
/**
* The single admission point every command passes through.
*
* <p>Validation order is fixed and each step is cheaper than the one after it, so an obviously
* inadmissible command is refused before anything is encoded or sent:
*
* <pre>{@code
* capability -> risk and permit provenance -> namespace -> slot -> request budget
* -> connection lane -> timeout/retry -> invocation -> reply budget -> translation -> telemetry
* }</pre>
*/
```
앞쪽 단계는 메모리 안에서 끝난다. 능력 확인과 허가 출처 확인과 네임스페이스 검사가 그렇다.
슬롯 계산과 요청 예산이 그다음이고, 그다음이 커넥션 레인 선택이다. 실제 호출은 그 뒤에 있다.
이 순서가 아니면 거부될 명령이 인코딩되고 전송된 뒤에 거부된다.
## 허가를 제시하는 것으로 충분하지 않다
```java
/**
* <p>Presenting a permit is never sufficient. The guard verifies who issued it and which policy it
* was issued for, so an application that implements the permit interface itself gets rejected with
* the same message as one that presents no permit at all.
*/
```
허가 인터페이스를 애플리케이션이 직접 구현하면 허가를 만들어 낼 수 있다. 그래서 가드가 발급자와 발급 정책을 확인한다.
거절 메시지가 같다는 것도 설계의 일부다. 자기가 만든 허가와 허가 없음이 구별되지 않으므로 우회 시도가 정보를 얻지 못한다.
## 가드가 들고 있는 협력자
```java
private final RedisCommandCatalog catalog;
private final RedisPermitVerifier permitVerifier;
private final RedisCapabilities capabilities;
private final RedisNamespace namespace;
private final RedisKeyRenderer keyRenderer;
private final ToIntFunction<String> slotCalculator;
```
카탈로그는 명령이 무엇인지 안다. 허가 검증기는 누가 발급했는지 안다. 능력은 이 배포가 무엇을 할 수 있는지 안다. 네임스페이스와 키 렌더러는 키가 어디에 속하는지 정한다. 슬롯 계산기는 클러스터 배치를 안다.
## 카탈로그의 지위
카탈로그는 명령의 정의를 담는다. 그 정의의 출처는 서버 메타데이터다. 정책 파일은 그 위에서 허용 범위를 정한다.
둘을 뒤집으면 정책 파일이 명령의 정의가 되고, 서버가 실제로 하는 일과 어긋날 수 있다.
:::tip
단일 승인 지점은 그것이 실제로 단일일 때만 의미가 있다. 우회 경로가 있으면 이 아홉 단계는 우회하지 않은 호출에만 적용되는 규칙이 된다.
:::
<!-- body:end -->