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>
12 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, evidenceCapturedOn, body, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | evidenceCapturedOn | body | assets | evidence | source | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | a19-f015-compatibilitymatrix-extension | CompatibilityMatrix 의 EXTENSION 등급을 쓰는 항목이 없다 | messaging-and-outbox | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:a19-f015-compatibilitymatrix-extension | 2026-09-04 | case-a19-f015-compatibilitymatrix-extension.body.md |
|
|
|
CompatibilityMatrix 의 EXTENSION 등급을 쓰는 항목이 없다
messaging-testkit 의 CompatibilityMatrix 가 어댑터 지원 등급을 Tier 셋으로 두는데 ENTRIES 다섯 중 EXTENSION 을 쓰는 항목이 0 이다. 그 등급 설명에 해당하는 messaging-spring-cloud-stream-bridge 는 브로커 버전을 인증하지 않아 Entry 생성자를 통과할 수 없다.
관계
- build-only 등급이 90개 파일의 미조립을 오늘의 사고에서 면제한다
그 기록은 빌드에만 참여하는 모듈이 조립 검사에서 빠지는 것을 다룬다. 이 leaf 도
runtime_memberships가 비어 있는데, 여기서 어긋난 것은 조립이 아니라 등급 표기다. - 기계가 읽는 능력 매니페스트와 사람이 읽는 등급표가 커서 서명에 대해 다르게 답한다 둘 다 코드가 든 표와 운영자가 읽는 문서 표가 같은 대상에 대해 서로 다른 값을 적고 있는 경우다.
- 지원 등급은 추론이 아니라 선언이고 증거 없이는 올라가지 않는다 그 결정은 증거가 있을 때만 등급을 올린다고 정한다. 여기서는 그 규칙 이전에, 등급 값 하나가 어떤 항목에도 붙지 못한 채 열거형에만 남아 있다.
문제
지원 등급이 messaging-testkit 의 CompatibilityMatrix 안에 표로 들어 있다.
세 등급 중 하나가 어느 항목에도 쓰이지 않는데, 그 등급 설명에 맞는 모듈이 저장소에 있다.
결론
Tier 값 셋 중 EXTENSION 을 쓰는 항목이 ENTRIES 에 없다. 다섯 항목은 messaging-kafka 하나가 STABLE 이고 나머지 넷이 EXPERIMENTAL 이다.
그 등급 설명이 말하는 어댑터 SPI 전용·지원 집합 밖에 해당하는 모듈은 messaging-spring-cloud-stream-bridge 다. main 6 파일 507 줄인데 표에 이름이 없다.
행이 되지 못하는 기계적 이유가 있다. Entry 의 간결 생성자는 브로커 버전 목록이 비면 거부한다. 이 모듈은 브로커가 아니라 Spring Cloud Stream 바인더 위의 이음매라 인증할 브로커 버전이 없다.
운영자용 docs/messaging/support-matrix.md 는 등급을 한 표로 관리하지 않는다. 앞쪽 표에서 Extension 이 붙은 행은 :37 하나뿐이다. 거기 적힌 어댑터 이름을, 코드 쪽 시험은 표에 없는 이름의 예시로 쓴다. 기능 등급 표(:66)의 :78 에는 이 bridge 가 있고 등급 칸이 Optional 인데, 그 낱말은 Tier 에 없다.
이 타입을 참조하는 main 코드는 자기 파일 하나뿐이다. 미등록 이름을 거부한다는 보장이 걸리는 범위는 시험과 계약 스위트다.
판정은 P3 이고 원문과 같다. 다만 원문은 이 bridge 가 운영자 문서에도 없다고 적었는데 :78 에 행이 있다.
검증 환경
확인 방식 : Tier 열거값과 각 자바독 확인, ENTRIES 항목 수와 등급별 계수, bridge leaf 의 main 파일 수·줄 수와 modules.json 의 runtime_memberships 확인, Entry 간결 생성자의 거부 조건 확인, of 의 거부 줄과 그것을 고정하는 시험 확인, 그 시험이 넘기는 이름 검색, CompatibilityMatrix 를 쓰는 main 코드 계수, 운영자 문서의 등급 표 전수와 각 표의 해당 행 확인, bridge 자바독과 정책 가드의 조건·예외 코드 확인 소스 수정 : x
재현 조건
- CompatibilityMatrix.Tier 의 값 셋과 각 값의 자바독을 읽는다.
- ENTRIES 의 항목을 세고 각 항목의 Tier 를 모은다.
- 등급별 사용 횟수를 집계해 쓰이지 않는 값을 찾는다.
- 그 값의 설명에 해당하는 모듈을 저장소에서 찾고 파일 수와 줄 수를 잰다.
- modules.json 에서 그 모듈의 runtime_memberships 를 읽는다.
- Entry 의 간결 생성자가 무엇을 거부하는지 읽고, 그 모듈이 그 조건을 만족할 수 있는지 본다.
- of 가 등록되지 않은 이름을 어떻게 처리하는지와 그것을 고정하는 시험, 그 시험이 넘기는 이름을 확인한다.
- CompatibilityMatrix 를 자기 파일 밖에서 쓰는 코드를 소스 세트별로 센다.
- 운영자 문서에서 등급 표를 전부 찾고, 각 표에서 그 등급 값과 그 모듈 이름이 나오는 행을 읽는다.
본문
messaging-testkit 의 CompatibilityMatrix 는 어댑터별 지원 등급을 코드 안의 표로 들고 있다. Tier 열거값은 셋이고, ENTRIES 는 다섯 항목이다.
Tier 셋과 ENTRIES 다섯
:::evidence key="a19-f015-compatibilitymatrix-extension" alt="저장소 루트에서 돌린 정적 검색 출력 73줄. CompatibilityMatrix.java 의 Tier 열거값 셋이 각각의 자바독과 함께 나오고, ENTRIES 다섯 항목의 어댑터 이름과 Tier 가 이어지며 STABLE 1 · EXPERIMENTAL 4 · EXTENSION 0 으로 집계된다. messaging-spring-cloud-stream-bridge 는 main 6 파일 507 줄에 modules.json 의 runtime_memberships 가 빈 리스트이고 ENTRIES 에 이름이 0 건이다. Entry 의 간결 생성자가 어댑터 이름 공백과 브로커 버전 목록 비어 있음을 각각 IllegalArgumentException 으로 거부하는 본문이 실리고, of 가 등록되지 않은 이름을 거부하는 줄과 그것을 고정하는 시험이 나오는데 그 시험이 미등록 이름의 예로 messaging-artemis 를 넘긴다. 같은 이름이 다른 시험의 픽스처와 계약 스위트에도 나오고, CompatibilityMatrix 를 자기 파일 밖에서 쓰는 main 코드는 0 건이다. 운영자 문서에는 등급 표가 브로커 등급과 기능 등급 둘로 있고 앞쪽의 유일한 Extension 행이 Artemis/JMS, 뒤쪽 78번 줄이 Spring Cloud Stream bridge 를 Optional 로 적는다. 마지막으로 bridge 자신의 자바독과 StreamBridgePolicyGuard 의 조건 넷이 각각의 예외 코드와 함께 나온다." caption="Tier 셋과 ENTRIES 의 등급 분포 · 표 밖의 bridge leaf · Entry 가 요구하는 브로커 버전 · 미등록 이름 거부와 그 시험이 쓰는 이름 · 자기 파일 밖 main 사용 0 · 운영자 문서의 등급 표 둘 · 가드의 조건 넷과 예외 코드 — 73줄 · exit 0" zoom="true" :::
:23~:32 의 Tier 는 STABLE, EXPERIMENTAL, EXTENSION 셋이다. :30 의 EXTENSION 에는 어댑터 SPI 전용이고 지원 집합 밖이라는 설명이 붙어 있다.
ENTRIES 는 다섯이다. messaging-kafka 가 STABLE(:85), 나머지 넷 — messaging-rabbit(:92), messaging-kafka-share-experimental(:97), messaging-pulsar-experimental(:104), messaging-nats-experimental(:109) — 이 EXPERIMENTAL 이다. Tier.EXTENSION 을 쓰는 항목은 0 이다.
ENTRIES 밖에 있는 messaging-spring-cloud-stream-bridge
messaging-spring-cloud-stream-bridge 는 main 6 파일 507 줄이고 ENTRIES 에 이름이 없다.
MessagingBindingBridge:8 의 자바독에는 상호운용 이음매이지 두 번째 메시징 API 가 아니라고 적혀 있다. StreamBridgePolicyGuard.validate 는 네 자리에서 거부한다 — :30 은 스위치가 꺼져 있으면 STREAM_BRIDGE_DISABLED, :36 은 순서 범위를 선언한 목적지를 STREAM_BRIDGE_ORDERING_UNSUPPORTED, :41 은 재시도 모드가 NONE 이 아니면 STREAM_BRIDGE_RETRY_UNSUPPORTED, :46 은 데드레터가 켜져 있으면 STREAM_BRIDGE_DLQ_UNSUPPORTED 로 던진다. 뒤 셋의 메시지는 그런 목적지가 네이티브 어댑터로 가야 한다고 적는다.
EXTENSION 자바독은 어댑터 SPI 전용이고 지원 집합 밖이라고 적고, 이 leaf 의 자바독은 두 번째 메시징 API 가 아니라고 적는다. 둘 다 지원 집합 밖에 두는 서술인데 이 leaf 는 ENTRIES 에 없다.
Entry 생성자가 브로커 버전을 요구한다
Entry 의 간결 생성자(:66~:74)는 어댑터 이름이 공백이면 거부하고(:67), 브로커 버전 목록이 비어 있으면 어댑터는 적어도 하나의 브로커 버전을 인증해야 한다는 메시지로 IllegalArgumentException 을 던진다(:70~:71).
이 leaf 는 브로커가 아니라 Spring Cloud Stream 바인더 위의 이음매다. 인증할 브로커 버전이 없으므로 지금 형태로는 ENTRIES 의 행이 될 수 없다. Tier.EXTENSION 이 비어 있는 것은 아무도 채우지 않아서만은 아니고, 그 등급 설명에 맞는 대상이 이 record 의 요구를 통과하지 못하기 때문이기도 하다.
미등록 이름을 of 에 넘겼을 때
of(:126)는 ENTRIES 에 없는 이름을 받으면 :129 에서 그 이름이 호환성 표에 없다는 메시지로 던진다. CompatibilityMatrixTest:83 의 anUnknownAdapterIsNotSilentlyTreatedAsSupported 가 그 동작을 고정하는데, 그 시험이 of 에 넘기는 미등록 이름이 "messaging-artemis" 다(:84).
운영자 문서의 두 등급 표
docs/messaging/support-matrix.md 에는 등급 표가 둘이다. :29 의 ## 브로커 등급 과 :66 의 ## 기능 등급 이다.
앞쪽 표에서 Extension 이 붙은 행은 하나뿐이고 :37 의 Artemis/JMS 다. 인증 기준은 범위 밖, Stable 기능은 adapter SPI만이라고 적혀 있다 — Tier.EXTENSION 자바독과 같은 내용이다. 그런데 코드의 시험은 같은 messaging-artemis 를 등록되지 않은 이름의 예로 쓴다.
뒤쪽 표 :78 에 이 leaf 가 있다. 등급 칸의 값은 Optional 이고, 그것은 Tier 에 없는 낱말이다.
이 표를 읽는 main 코드가 없다
CompatibilityMatrix 를 자기 파일 밖에서 쓰는 main 코드는 0 건이다. of 의 거부도 hasLiveBrokerCertification(:60)도 시험과 계약 스위트 안에서만 불린다. 등록되지 않은 이름을 조용히 지원으로 두지 않는다는 보장은 시험 경로에 대한 것이지 런타임에 대한 것이 아니다.
원문과 갈리는 자리
원문은 이 leaf 가 코드의 표에도 운영자 문서의 표에도 없다고 적었다. 문서 쪽은 그렇지 않다. :78 에 행이 있고 등급 칸이 Optional 로 채워져 있다. 원문이 본 것은 :29 의 브로커 등급 표이고, 이 leaf 는 :66 의 기능 등급 표에 있다.
원문은 Tier.EXTENSION 이 쓰이지 않는 것을 미조립의 한 사례로 묶었다. runtime_memberships 가 비어 있는 것은 맞지만, 이 record 에 대해서는 Entry 의 브로커 버전 요구가 별도의 이유로 작용한다.
확인하지 못한 것
JMS 계열 어댑터가 다른 모듈명으로 존재할 가능성은 좁히지 않았다. src/messaging 아래에 해당 디렉터리가 없다는 데까지다.
EXTENSION 항목을 실제로 추가해 생성자에서 거부되는 것을 실행으로 보이지 않았다. 거부 조건을 코드로 읽은 데까지다.
두 등급 표의 어휘가 다른 것이 의도인지 확인하지 않았다.