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,120 @@
---
kind: CASE
slug: a-soak-threshold-written-for-a-grade-that-does-not-exist
title: 담금 임계값이 열거형에 없는 등급을 위해 쓰여 중간 등급으로 올라가는 것이 더 어려워졌다
topic: self-disclosure-grading
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:a-soak-threshold-written-for-a-grade-that-does-not-exist
evidenceCapturedOn: 2026-09-02
assets:
- key: a-soak-threshold-written-for-a-grade-that-does-not-exist
file: ../../../final/evidence/rendered/a-soak-threshold-written-for-a-grade-that-does-not-exist.svg
evidence:
- ../../../final/evidence/raw/a-soak-threshold-written-for-a-grade-that-does-not-exist.txt
source:
- 분석 문서는 grpc-advanced-bootstrap 편 §17.4 다.
---
# 담금 임계값이 열거형에 없는 등급을 위해 쓰여 중간 등급으로 올라가는 것이 더 어려워졌다
승격 게이트가 담금 기간 임계값 둘을 갖는다. 긴 쪽은 열거형에 없는 등급을 위해 쓰였고, 선택이 그 등급이 아닌 나머지 전부를 긴 쪽으로 보낸다. 그래서 담금 기록이 가장 적을 등급이 밟아야 하는 첫 걸음이 상위 등급으로 가는 것보다 엄격해졌다.
## 관계
- **등급은 네 단계로 나누고 관측보다 높게 적지 않는다**
같은 주제의 규칙이고 이 사례는 그 반대편이다. 여기서는 낮은 등급으로 올라가는 것이 더 어렵다.
- **지원 등급은 추론이 아니라 선언이고 증거 없이는 올라가지 않는다**
등급 체계를 코드로 두는 결정이다.
- **이름이 검사한다고 말하는 것을 본문이 검사하지 않는 테스트 넷**
이 결함을 덮은 테스트가 그 목록의 넷째다.
## 문제
게이트 javadoc 의 모형은 등급 둘이다. 하나는 능력이 동작하고 문서화되었다는 것이고, 다른 하나는 모든 배포가 그것을 받게 된다는 것이다. 둘째가 의존성을 모든 클래스패스에 올리고 실패 양식을 모든 당직에 올리므로 첫째에 더해 더 긴 담금을 요구한다는 설명이 붙어 있다.
상수도 둘이다. 짧은 쪽이 이레, 긴 쪽이 서른 날이다.
## 결론
둘째 등급이 열거형에 없다. 값은 넷이고 그중 어느 것도 그 이름이 아니다.
담금 선택은 목표 등급이 첫째 등급이면 짧은 쪽, 아니면 긴 쪽이다. 그러므로 첫째가 아닌 나머지 세 등급 전부가 긴 쪽으로 떨어진다.
증거 항목 일곱 개는 목표 등급과 무관하게 요구된다. 그래서 결과가 뒤집힌다. 중간 등급에서 첫째 등급으로 올라가려면 일곱 항목과 이레가 필요하고, 추적 등급에서 중간 등급으로 올라가려면 일곱 항목과 서른 날이 필요하다.
같은 메서드의 셋째 차단 사유가 추적 등급의 능력은 다른 무엇보다 먼저 중간 등급을 밟아야 한다고 적는다. 그 강제된 첫 걸음이 상위 등급으로 가는 것보다 엄격하다. 추적 등급의 뜻이 추적할 뿐 구현되지 않았다는 것이므로, 정의상 담금 기록이 가장 적을 등급에 가장 긴 담금이 붙는다.
테스트가 그 뒤틀림을 그대로 보여 준다. 긴 갈래를 실행하려고 고른 전이가 철회 방향인데 표시 이름은 둘째 등급이 되는 것이라고 말한다. 그리고 중간 등급으로 올라가는 테스트는 담금을 예순 날로 준다.
## 검증 환경
OpenJDK : 21.0.12
Gradle : 9.0.0
확인 방식 : 평가 메서드 본문과 등급 열거형 대조, 두 테스트가 고른 숫자 확인, 해당 테스트 실행
소스 수정 : x
## 재현 조건
1. 승격 게이트의 두 담금 상수와 그 javadoc 의 등급 모형을 읽는다.
2. 등급 열거형의 값을 전부 나열하고 javadoc 의 둘째 등급이 그 안에 있는지 센다.
3. 증거 검사와 담금 선택을 잇는 구간을 읽고, 어느 쪽에 목표 등급 조건이 있는지 확인한다.
4. 추적 등급 차단 사유의 조건과 문구를 확인한다.
5. 긴 갈래를 실행하는 테스트가 고른 전이와 표시 이름을 대조하고, 중간 등급 테스트가 담금에 넣은 값을 확인한다.
6. 그 테스트들을 실행한다.
## 본문
<!-- body:start -->
승격 게이트는 능력 하나의 등급 이동을 판정한다. 그 판정에 담금 기간 임계값이 둘 들어간다.
## javadoc 이 말하는 둘째 등급이 열거형에 없다
:::evidence key="a-soak-threshold-written-for-a-grade-that-does-not-exist" alt="코드베이스에서 승격 게이트 javadoc 의 두 등급 모형과 담금 상수 둘, 등급 열거형의 값 넷과 둘째 등급에 해당하는 값의 수, 증거 검사와 담금 선택을 잇는 여섯 줄과 증거 항목 일곱, 추적 등급이 중간 등급을 먼저 밟게 하는 차단 사유, 긴 갈래를 실행하는 테스트가 고른 전이와 표시 이름, 중간 등급으로 올라가는 테스트가 준 담금 값, 그리고 그 테스트들을 실제로 돌린 결과를 뽑은 출력 66줄. 증거 검사에는 목표 등급 조건이 없고 담금 선택에만 있다는 것이 그 여섯 줄에 그대로 보인다." caption="두 등급 모형과 상수 둘 · 열거형 값 넷 · 증거 검사에는 목표 조건 없음 · 테스트가 고른 전이와 담금 · 실행 결과 8건 전부 통과 — 66줄 · exit 0" zoom="true"
:::
javadoc 의 모형은 이렇다. 첫째 등급에 도달한다는 것은 능력이 동작하고 문서화되었다는 뜻이고, 둘째 등급이 된다는 것은 모든 배포가 그것을 받아 의존성이 모든 클래스패스에 오르고 실패 양식이 모든 당직에 오른다는 뜻이다. 그래서 둘째는 첫째에 더해 더 긴 담금을 요구한다.
상수 이름도 그 모형을 따른다. 하나는 첫째 등급용 이레, 다른 하나는 둘째 등급용 서른 날이다.
등급 열거형의 값은 넷이다. 증거가 갖춰진 것(첫째 등급), 실패 양식이 규명되지 않은 것(중간 등급), 추적만 하는 것(추적 등급), 철회되었거나 거부된 것이다. 둘째 등급에 해당하는 값은 0 이다.
## 그래서 조건이 나머지 세 등급을 전부 받는다
담금을 고르는 식은 삼항 하나다. 목표 등급이 첫째 등급이면 이레, 아니면 서른 날.
없는 등급을 위해 만든 갈래가 있는 세 등급을 전부 받는다. 중간 등급으로 올라가는 것도, 추적 등급으로 내려가는 것도, 철회되는 것도 서른 날이다.
## 증거 요구는 목표를 보지 않는다
증거 검사와 담금 선택이 여섯 줄 안에 나란히 있다. 앞의 셋은 빠진 증거를 그대로 차단 사유로 옮기고, 목표 등급을 보지 않는다. 뒤의 둘만 목표 등급을 본다.
요구되는 항목은 일곱이다. 호환성 증거, 보안 검토, 결함 증거, 성능 증거, 결정 기록, 런북, 실환경 시험이다.
그래서 두 승격의 차이가 담금 기간뿐이 되고, 그 기간이 뒤집혀 있다. 중간 등급에서 위 등급으로 올라가는 데 이레, 추적 등급에서 중간 등급으로 올라오는 데 서른 날이다.
## 담금 기록이 가장 적을 등급에 가장 긴 담금이 걸린다
같은 메서드의 셋째 차단 사유가 추적 등급을 다룬다. 추적 등급의 능력은 다른 무엇보다 먼저 중간 등급이 되어야 한다는 것이다.
그 강제된 첫 걸음의 목표가 첫째 등급이 아니므로 서른 날이 걸린다.
추적 등급의 뜻은 추적할 뿐 구현되지 않았다는 것이다. 정의상 담금 기록이 가장 적을 등급에 가장 긴 담금이 붙는다.
## 테스트가 그 뒤틀림을 그대로 보여 준다
긴 임계값을 실행하는 테스트가 있다. 표시 이름은 둘째 등급이 되는 것이 첫째 등급이 되는 것보다 긴 담금을 요구한다고 말한다.
실제로 고른 전이는 첫째 등급에서 철회로 가는 것이다. 그 전이가 긴 갈래에 떨어지는 이유는 둘째 등급이어서가 아니라 첫째 등급이 아니어서다. 이름이 가리키는 전이와 검사하는 전이가 다르다.
추적 등급 테스트도 마찬가지다. 중간 등급으로 올라가는 쪽이 통과해야 하는데, 담금에 예순 날을 넣는다. 요구치의 두 배다.
두 테스트를 돌리면 여덟 건이 전부 통과한다. 무엇을 통과시키는지는 단언 대상이 정하고, 여기서는 그 대상이 이름과 다르다.
## 확인하지 못한 것
담금 값을 바꿔 가며 게이트를 직접 불러 보지는 않았다. 판정은 갈래 조건과 두 테스트가 고른 숫자에 근거하고, 위 실행은 저장소가 가진 테스트를 그대로 돌린 것이다. 이레를 넣었을 때 무엇이 실패하는지는 갈래 조건으로 판정했다.
<!-- body:end -->
@@ -0,0 +1,135 @@
---
kind: CASE
slug: a-startup-validator-that-requires-a-key-nothing-signs-with
title: 시작 검증기가 커서 서명 키를 요구하는데 그 키로 서명하는 코드가 없다
topic: self-disclosure-grading
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:a-startup-validator-that-requires-a-key-nothing-signs-with
evidenceCapturedOn: 2026-09-02
assets:
- key: a-startup-validator-that-requires-a-key-nothing-signs-with
file: ../../../final/evidence/rendered/a-startup-validator-that-requires-a-key-nothing-signs-with.svg
evidence:
- ../../../final/evidence/raw/a-startup-validator-that-requires-a-key-nothing-signs-with.txt
source:
- 분석 문서는 inbound-graphql 어댑터 편 §24.1 이다. 서명하는 코드가 없다는 사실은 같은 문서 §23.2, 액추에이터 엔드포인트가 등록되지 않는다는 것은 §8.1, 매니페스트의 네 번째 신호는 §29.2 다. websocket 어댑터 편 §18.3 이 같은 형태의 재개 토큰 서명을 대조한다.
---
# 시작 검증기가 커서 서명 키를 요구하는데 그 키로 서명하는 코드가 없다
시작 검증기가 프로덕션에서 커서 서명 키를 요구하고, 그 검증기는 실제로 돈다. 그 키로 서명하는 코드는 없고, 서명될 커서도 아직 없다.
## 관계
- **등급표 13행 중 일곱을 스스로 강등하고 한 행만 관측과 어긋났다**
등급표가 이 능력을 모형 단계로 적고 있다.
- **하위 시스템 전체가 미배선인데 그것을 켜는 플래그는 시작 검사를 수행한다**
같은 형태가 mongo 어댑터에서 나타난 사례다.
- **커서에 서명하는 이유는 기밀성이 아니라 무결성이다**
이 커서 서명이 지키려는 성질이다.
## 문제
시작 검증기가 프로덕션 배포에서 커서 서명 키를 요구한다. 없으면 기동을 거절하고 이유를 적는다 — 서명되지 않은 커서는 클라이언트가 편집할 수 있다는 것이다.
이 검증기는 자동설정이 실제로 부른다.
## 결론
그 키를 소비하는 프로덕션 코드는 둘이다. 거절하는 검증기가 하나, 보고하는 액추에이터가 하나다. 뒤쪽을 만드는 코드는 아직 없다.
그 키로 서명하는 코드는 없다. 커서 코덱 쪽도 같다. 구현이 하나인데 만드는 프로덕션 코드가 없다.
서명될 커서도 아직 없다. 커넥션 조립기를 만드는 프로덕션 코드가 0 이고, 배포되는 스키마는 상태 확인 필드 하나다.
그런데 릴리스 게이트가 읽는 매니페스트는 이 능력을 안정 등급 승인 목록에 넣어 두었다.
즉 결함은 능력이 미완이라는 것이 아니다. 미완인 능력에 대해 플랫폼이 요구하고 승인해 주는 신호를 이미 켜 두었다는 것이다.
닫는 것은 배선 결정이 아니라 설계 결정이다. 키링 팩토리는 키 재료를 인자로 받는데, 설정은 키 자체가 설정에 나타나지 않는다고 일부러 정해 두었다.
그때까지의 최소 조치는 요구를 남기고 문구를 사실에 맞추는 것이다. 테스트의 네 번째 케이스가 요구를 남기라고 못 박는다.
## 검증 환경
OpenJDK : 21.0.12
확인 방식 : 설정 소비 지점과 생성 지점의 계수, 배포 스키마 확인, 컨텍스트 테스트 확인
소스 수정 : x
## 재현 조건
1. 시작 검증기가 프로덕션을 거절하는 조건과 문구를 읽고, 자동설정이 그것을 부르는 지점을 확인한다.
2. 그 키 설정을 소비하는 프로덕션 코드를 세고, 그중 만들어지지 않는 것이 있는지 확인한다.
3. 커서 코덱 구현체와 그것을 만드는 프로덕션 코드를 센다.
4. 커넥션 조립기를 만드는 코드를 세고, 배포되는 스키마를 읽는다.
5. 릴리스 게이트 매니페스트의 승인 목록을 확인한다.
6. 부재를 고정하는 테스트와 요구를 남기라고 적은 케이스를 읽는다.
## 본문
<!-- body:start -->
프로덕션 배포에 커서 서명 키가 없으면 시작 검증기가 기동을 거절한다. 이유도 함께 적는다 — 서명되지 않은 커서는 클라이언트가 편집할 수 있다.
이 검증기는 발행만 되고 마는 종류가 아니다. 자동설정이 실제로 부른다.
## 프로덕션이 요구하고, 운영자가 주고, 아무것도 서명하지 않는다
:::evidence key="a-startup-validator-that-requires-a-key-nothing-signs-with" alt="코드베이스에서 시작 검증기가 커서 서명 키 없이 프로덕션을 거절하는 문구와 그 검증기가 자동설정에서 실제로 불리는 지점, 그 키 설정을 소비하는 두 곳과 그중 액추에이터 엔드포인트를 만드는 코드 수, 커서 코덱 구현체 수와 그것을 만드는 코드 수, 커넥션 조립기를 만드는 코드 수와 배포되는 스키마 전체, 릴리스 게이트가 읽는 매니페스트의 승인 목록, 부재를 고정하는 테스트의 단언과 요구를 남기라고 적은 네 번째 케이스, 그리고 등급표가 이 능력에 매긴 등급을 뽑은 출력. 배포되는 스키마에 커서가 없고 서명 코덱을 만드는 코드도 0 이라는 것이 그 출력에 보인다." caption="검증기의 거절 문구와 실제 호출 · 소비 두 곳과 미조립 엔드포인트 · 코덱 생성 0 · 스키마는 _health 하나 · 매니페스트의 승인 · 요구를 남기라는 테스트" zoom="true"
:::
그 키 설정을 읽는 프로덕션 코드는 둘이다. 없으면 거절하는 검증기와, 설정되었다고 보고하는 액추에이터 엔드포인트다.
뒤쪽은 아직 만들어지지 않는다. 그 엔드포인트를 생성하는 프로덕션 코드가 0 이고, 그 클래스의 javadoc 이 등록을 컴포지션 루트의 몫으로 남겨 두었다.
서명하는 코드는 없다. 커서 코덱 구현은 하나뿐이고, 그것과 키링을 만드는 프로덕션 코드가 0 이다.
## 서명될 커서 자체가 아직 없다
커넥션 조립기를 만드는 프로덕션 코드가 0 이다.
배포되는 스키마는 상태 확인 필드 하나다. 그 파일의 주석이 왜 그런지 적는다 — 이 모듈은 기능 없이도 독립으로 부팅해야 하고, Spring for GraphQL 은 빈 스키마로는 시작하지 않으며, 스켈레톤은 기능 타입 이름을 절대 대면 안 된다는 것이다.
커서는 발급되지도 소비되지도 않는다. 검증기 문구가 막는다고 말한 상태는 지금 존재하지 않는다.
## 그런데 승인 신호는 이미 켜져 있다
릴리스 게이트가 읽는 능력 매니페스트가 있다. 안정 스타터에서 켜도 되는 능력의 목록이다.
서명 커서 커넥션이 그 목록에 들어 있다. 켜도 된다고 판정되고, 켜는 코드는 없다.
## 결함은 미완이 아니다
컨텍스트 테스트가 그 부재를 고정한다. 설정에 키가 담기는 것을 먼저 확인하고, 그다음 코덱 빈도 키링 빈도 없다는 것을 단언한다.
단언 문구가 판정을 적는다 — 아무것도 지키지 않는 키가 없다는 이유로 프로덕션이 기동을 거절하는 것은, 그 키를 아예 요구하지 않는 것보다 나쁘다.
같은 테스트가 두 번째 능력도 같은 방식으로 고정한다. 변이 멱등성 인터셉터가 어떤 설정에서도 참조되지 않는다. 등급표가 둘 다 모형 단계로 적어 두었다.
미완인 능력을 미완이라고 적는 것과, 미완인 능력에 대해 요구하고 승인해 주는 신호를 켜 두는 것은 다르다.
## websocket 어댑터의 같은 형태는 결함이 아니다
websocket 어댑터에 재개 토큰 코덱과 키링이 있다. 이 둘도 조립되지 않는다.
차이는 하나다. websocket 어댑터에는 키를 요구하는 시작 검증기가 없다. 그래서 잘못된 확인 신호도 없다.
미배선은 양쪽이 같고 결함은 한쪽에만 있다. 결함을 만드는 것이 미완이 아니라 검증기라는 것을 그 대조가 보여 준다.
## 닫는 것은 배선이 아니라 설계다
키링 팩토리는 키 재료 자체를 인자로 받는다. 그런데 설정은 키 자체가 설정에 절대 나타나지 않는다고 일부러 정해 두었다.
키 재료가 어디서 오는지를 먼저 정해야 무엇을 어디에 붙일지가 정해진다.
그동안 할 수 있는 일이 없는 것은 아니다. 요구는 남기고 문구를 사실에 맞추면 된다.
테스트의 네 번째 케이스가 그것을 못 박는다. 요구는 지우지 말고 남기라는 것, 요구 자체는 옳고 빠진 절반은 구현이라는 것, 커서 서명이 요청 경로에 닿으면 앞의 두 케이스는 뒤집히고 이 케이스만 그대로 남는다는 것이다.
## 확인하지 못한 것
키 없이 기동해 거절을 재현해 보지는 않았다. 참조 계수를 이름 기반 정적 검색으로 냈기 때문에 리플렉션 조립 경로까지는 배제하지 못했다.
<!-- body:end -->
@@ -0,0 +1,94 @@
---
kind: CASE
slug: build-only-exempts-ninety-files-from-todays-incident
title: build-only 등급이 90개 파일의 미조립을 오늘의 사고에서 면제한다
topic: self-disclosure-grading
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:build-only-exempts-ninety-files-from-todays-incident
evidenceCapturedOn: 2026-09-01
assets:
- key: build-only-exempts-ninety-files-from-todays-incident
file: ../../../final/evidence/rendered/build-only-exempts-ninety-files-from-todays-incident.svg
evidence:
- ../../../final/evidence/raw/build-only-exempts-ninety-files-from-todays-incident.txt
source:
- 원본 분석 절은 final/document.md#8-3 · analysis/17 §4.1, §26.6 이다.
---
# build-only 등급이 90개 파일의 미조립을 오늘의 사고에서 면제한다
웹소켓 어댑터가 어떤 런타임 컴포지션에도 속하지 않는다. 그 리프의 미조립 파일들은 지금 배포되지 않으므로 오늘의 사고가 아니고, 배선되는 날의 목록이다.
## 관계
- **runtime_memberships를 먼저 읽고 심각도를 정한다**
이 사례가 그 규칙의 대표 형태다.
- **gRPC 플랫폼은 build-only로 두고 애플리케이션 도달 경로를 먼저 정한다**
같은 상태를 다른 가족이 결정으로 채택한 사례다.
- **등급표 13행 중 일곱을 스스로 강등하고 한 행만 관측과 어긋났다**
자기 상태를 정확히 공시하는 같은 계열이다.
## 문제
웹소켓 어댑터 리프에 미조립 파일이 다수 있다. 정책과 상태 기계는 있고 전송 핸들러가 없는 형태다.
이 상태의 심각도를 어떻게 판정할 것인가.
## 결론
레지스트리를 먼저 읽으면 답이 갈린다.
이 리프의 런타임 멤버십이 비어 있다. 어떤 배포에도 들어가지 않는다.
그러므로 미조립은 오늘의 프로덕션 영향이 0 이다. 없는 것을 쓸 수 없기 때문이다.
그리고 저장소가 그 상태를 인정하고 고정한다. 조건부 전송 조립 계약 테스트가 빌드 전용 전송 목록을 갖고, 그 목록에 이 리프가 들어 있다.
즉 배포되지 않는다는 사실이 규약이 아니라 테스트로 고정되어 있다.
남는 것은 배선되는 날의 목록이다. 그날 이 파일들의 미조립이 한꺼번에 오늘의 사고가 된다. 그때 함께 연결해야 할 것들이 지금 기록되어 있어야 한다.
이 판정 방식이 이 저장소 전체에 적용된다. 같은 형태의 발견이라도 그 리프가 배포되는지에 따라 심각도가 달라지고, 그 판단의 근거는 레지스트리의 런타임 멤버십이다.
## 검증 환경
OpenJDK : 21.0.12
확인 방식 : 레지스트리 항목 확인과 계약 테스트 목록 대조
소스 수정 : x
## 재현 조건
원문은 final/evidence/raw/228-app-bootstrap-activation-probes.txt 에 있다.
1. 레지스트리에서 웹소켓 어댑터의 런타임 멤버십을 확인한다. 비어 있다.
2. 조건부 전송 조립 계약 테스트의 빌드 전용 전송 목록을 확인한다.
3. 그 목록에 이 리프가 있는지 확인한다.
## 본문
<!-- body:start -->
websocket leaf의 `backend.websocket` 플랫폼 약 90개 main 파일에 조립 지점이 없고, 세 안전 장치(`WebSocketPlatformStartupValidator` 125줄 · `WebSocketStackExclusivity` 78줄 · settings의 safe-default 규약)가 전부 호출자 0이다.
## WebSocketPlatformStartupValidator 참조 위치
:::evidence key="build-only-exempts-ninety-files-from-todays-incident" alt="코드베이스에서 WebSocketPlatformStartupValidator 를 검색한 출력 2줄. 이 기록이 세는 참조가 그 출력에 그대로 보인다." caption="WebSocketPlatformStartupValidator 코드베이스 검색 — 2줄 · exit 0" zoom="true"
:::
## P1에서 P2로 내린 근거
처음에 P1로 기록했다가 `runtime_memberships=[]`가 기계로 강제되는 build-only 등급임을 확인하고 내렸다 — 그 실패 시나리오가 현재 출하되는 두 조합 어디에서도 발생할 수 없다.
## 이 Case의 요점
등급이 심각도를 바꾸되 발견을 없애지는 않는다.
## 확인하지 못한 것
배선했을 때 실제로 무엇이 깨지는지 확인하지 않았다. 이 기록은 현재 심각도 판정에 대한 것이다.
없음
<!-- body:end -->
@@ -0,0 +1,100 @@
---
kind: CASE
slug: seven-rows-downgraded-by-the-family-itself
title: 등급표 13행 중 일곱을 스스로 강등하고 한 행만 관측과 어긋났다
topic: self-disclosure-grading
project: clean-architecture-backend-template
status: 게시 전
sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916
rootTreeNode: case:seven-rows-downgraded-by-the-family-itself
evidenceCapturedOn: 2026-09-01
assets:
- key: seven-rows-downgraded-by-the-family-itself
file: ../../../final/evidence/rendered/seven-rows-downgraded-by-the-family-itself.svg
evidence:
- ../../../final/evidence/raw/seven-rows-downgraded-by-the-family-itself.txt
source:
- 원본 분석 절은 final/document.md#7-5 · analysis/16 §20.1, §29.1, §45.2 이다.
---
# 등급표 13행 중 일곱을 스스로 강등하고 한 행만 관측과 어긋났다
GraphQL 리프의 능력 등급표는 열세 행 중 일곱을 스스로 낮게 적는다. 분석이 그 표를 관측과 대조했을 때 어긋난 것은 한 행뿐이었다.
## 관계
- **네 단계 공시 등급 — modelled에서 production-verified까지**
이 표가 쓰는 등급 체계다.
- **등급은 네 단계로 나누고 관측보다 높게 적지 않는다**
이 사례가 검증한 규칙이다.
- **시작 검증기가 커서 서명 키를 요구하는데 그 키로 서명하는 코드가 없다**
어긋난 행과 관련된 사례다.
## 문제
능력 등급표는 자기 신고다. 작성자가 자기 코드의 성숙도를 적는다.
자기 신고는 대개 낙관적이다. 그래서 이런 표를 검증할 때의 기대는 실제보다 높게 적혀 있으리라는 것이다.
## 결론
반대였다.
열세 행 중 여섯 행이 스스로 modelled 로 적혀 있다. 그 등급의 정의는 정책과 계약 객체가 있고 단위 테스트가 있으며 요청 경로에는 없다는 것이다.
객체 인가는 답하는 계약을 중립 계층이 소유하고 이 리프는 매핑만 가지며 실행 경로에 연결하는 설정이 없다고 적는다
커서 서명은 자동설정이 둘 중 무엇도 생성하지 않는다고 적고 그 사실을 테스트가 고정한다고 덧붙인다
변경 멱등성은 인터셉터를 참조하는 설정이 없다고 적는다
지속 연산은 durable 구현체가 미제공이라고 적는다
구독과 웹소켓과 SSE 와 RSocket 은 정책과 상태 기계 단위 테스트만 있고 전송 핸들러가 없다고 적으며, 그래서 타입 이름도 승인 계열이라고 덧붙인다
페더레이션 계열은 단위 테스트만 있다고 적는다
일곱 번째 행은 등급조차 아니다. 실부하와 장애는 미달성으로 적히고, 증거가 없으면 릴리스 게이트가 거부한다는 사실이 함께 있다.
분석이 이 표를 관측과 대조했을 때 어긋난 것은 한 행뿐이었다.
이 결과가 이 사례의 내용이다. 자기 신고 등급표가 관측과 거의 일치했고, 그 일치는 등급을 낮게 적는 규칙과 증거 칸에 구체적인 수를 적는 관행에서 나왔다.
부재를 테스트로 고정하는 방식도 그 일치에 기여한다. 자동설정이 아무것도 생성하지 않는다는 사실을 테스트가 붙들고 있으므로, 나중에 누군가 배선하면 그 테스트가 깨지고 등급표를 함께 갱신하게 된다.
## 검증 환경
OpenJDK : 21.0.12
확인 방식 : 등급표 각 행과 해당 코드 및 테스트 대조
소스 수정 : x
## 재현 조건
원문은 final/evidence/raw/213-inbound-graphql-release-probes.txt 에 있다.
1. 리프의 안내 문서에서 등급 정의와 등급표를 읽는다.
2. 각 행의 등급과 증거 칸을 확인한다.
3. modelled 로 적힌 행에 대해 실제로 요청 경로에 없는지 확인한다.
4. wired 로 적힌 행에 대해 증거 칸이 지목하는 테스트를 확인한다.
## 본문
<!-- body:start -->
`CLAUDE.md`가 4등급을 정의하고 "현재 등급보다 높게 표현하지 않는다"를 규칙으로 선언한 뒤 13행 중 일곱을 스스로 `modelled`로 강등했다.
## 스스로 강등한 일곱 행
:::evidence key="seven-rows-downgraded-by-the-family-itself" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true"
:::
## 전수 대조 결과 12행이 일치했다
sub-scope 02~06의 파일 단위 배선 데이터와 13행을 대조했다. `요청 크기/Accept 협상 (http/)` 한 행만 불일치이고 **어긋나는 방향이 표가 금지한 "높게 표현" 쪽**이다 — 인용된 두 증거가 endpoint 테스트가 아닌 순수 단위 테스트이고 대상 타입은 미배선이다.
## 기계 매니페스트와 사람이 읽는 표가 갈린다
커서에 대해 서로 다른 답을 준다.
## 확인하지 못한 것
wired 로 적힌 행 전부에 대해 그 테스트를 실행하지 않았다. 확인한 것은 테스트의 존재와 그것이 무엇을 단언하는지다.
없음 — 13행 전수 대조
<!-- body:end -->