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>
126 lines
8.8 KiB
Plaintext
126 lines
8.8 KiB
Plaintext
EVD-334 기존 18개 리프의 test lane 재실행 (HEAD 21234e38)
|
|
측정일 2026-08-31 · workmachine
|
|
|
|
────────────────────────────────────────────────────────────────────────
|
|
1. 결과 요약
|
|
────────────────────────────────────────────────────────────────────────
|
|
모듈 classes tests fail err skip
|
|
adapter/outbound/persistence-jpa 94 477 0 0 0
|
|
adapter/outbound/persistence-mongo 14 72 0 0 0
|
|
adapter/outbound/cache-redis 52 435 0 0 0
|
|
adapter/outbound/fileserver 31 398 1 0 3
|
|
adapter/outbound/objectstorage 47 140 0 0 0
|
|
adapter/outbound/notification 54 327 0 0 1
|
|
adapter/inbound/web 131 895 0 0 0
|
|
adapter/inbound/graphql 92 793 0 0 0
|
|
adapter/inbound/websocket 64 513 0 0 0
|
|
app-bootstrap 169 1016 1 0 4
|
|
adapter/outbound/httpclient 61 283 3 0 0 (EVD-332)
|
|
|
|
1차 배치(별도 실행, 전부 통과): domain-core · shared-contract · application-core ·
|
|
adapter:outbound:support · identifier · messaging · adapter:inbound:grpc
|
|
BUILD SUCCESSFUL in 11s
|
|
|
|
합계 실패 5건 = httpclient 3 + fileserver 1 + app-bootstrap 1.
|
|
아래 셋 모두 프로덕션 결함이 아니라 실행 환경 결손이다. 각각 원인을 측정으로 확정했다.
|
|
|
|
────────────────────────────────────────────────────────────────────────
|
|
2. fileserver 1건 — JVM 경로 인코딩
|
|
────────────────────────────────────────────────────────────────────────
|
|
LocalPersistentPayloadOperationsTest
|
|
> inspectsLegacyRootArtifactByBoundedNoFollowStreamingWithoutRewritingIt()
|
|
|
|
java.nio.file.InvalidPathException: Malformed input or input contains unmappable characters:
|
|
월간 export -- legacy 01.csv
|
|
at sun.nio.fs.UnixPath.encode(UnixPath.java:129)
|
|
at LocalPersistentPayloadOperationsTest.java:331
|
|
|
|
컨테이너 기본 환경:
|
|
LANG= LC_CTYPE="POSIX"
|
|
file.encoding = UTF-8
|
|
native.encoding = ANSI_X3.4-1968
|
|
sun.jnu.encoding = ANSI_X3.4-1968 ← 경로 인코딩은 이쪽을 쓴다
|
|
|
|
동일 컨테이너에 LANG=C.utf8 / LC_ALL=C.utf8 을 주고 같은 test 만 재실행:
|
|
sun.jnu.encoding = UTF-8
|
|
gradle exit=0 ← 통과
|
|
|
|
판정: 환경. 한글 파일명을 쓰는 test 가 UTF-8 로케일을 전제하는데, 그 전제를 선언하지 않는다.
|
|
저장소는 sun.jnu.encoding 을 test JVM 에 고정하지 않는다.
|
|
|
|
────────────────────────────────────────────────────────────────────────
|
|
3. app-bootstrap 1건 — jq 미설치가 "계약 위반"으로 보고된다
|
|
────────────────────────────────────────────────────────────────────────
|
|
ComposeMergeCharacterizationTest > every lane renders exactly the services its contract names
|
|
|
|
org.opentest4j.AssertionFailedError: [verify-compose-profile-contracts.sh said:
|
|
jq is required
|
|
]
|
|
expected: 0
|
|
but was: 78
|
|
at ComposeMergeCharacterizationTest.java:62
|
|
|
|
which jq → NOT INSTALLED
|
|
|
|
스크립트는 전제 결손과 계약 위반을 다른 코드로 구분한다:
|
|
scripts/verify-compose-profile-contracts.sh:28-30
|
|
command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 78; }
|
|
command -v docker >/dev/null 2>&1 || { echo "docker is required" >&2; exit 78; }
|
|
[[ -r "${CONTRACTS}" ]] || { echo "missing ${CONTRACTS}" >&2; exit 78; }
|
|
…
|
|
스크립트 끝: FAILURES > 0 이면 exit 1
|
|
|
|
test 는 docker compose 존재만 assumption 으로 막고, 나머지 두 전제는 막지 않는다:
|
|
ComposeMergeCharacterizationTest.java:58-62
|
|
Assumptions.assumeTrue(dockerComposeIsAvailable(), "docker compose is not on this machine");
|
|
ProcessResult result = run(List.of("./scripts/verify-compose-profile-contracts.sh"));
|
|
assertThat(result.exitCode()) … .isZero();
|
|
|
|
exit 78(sysexits.h 의 EX_CONFIG, "확인할 수 없음")과 exit 1("계약이 틀렸음")이 같은 실패로 뭉개진다.
|
|
jq 없는 기계에서 이 suite 는 아무것도 검사하지 않고 "레인이 계약과 다르다"고 보고한다.
|
|
|
|
────────────────────────────────────────────────────────────────────────
|
|
4. 그 계약이 실제로 성립하는지 — 독립 경로 검증
|
|
────────────────────────────────────────────────────────────────────────
|
|
jq 를 설치하지 않았다(사용자 기계의 시스템 변경). 대신 스크립트의 6개 검사를 파이썬으로 이식해
|
|
같은 docker compose 렌더링 위에서 돌렸다. 아무것도 기동하지 않는 정적 검사다.
|
|
|
|
compose version 2.40.3+ds1-0ubuntu1~24.04.1 >= 2.24.4
|
|
off-local: app @ local
|
|
off-dev: app @ dev
|
|
off-prod: app @ prod
|
|
local-jpa: app,db @ local
|
|
local-mongo: app,mongo,mongo-rs-init @ local
|
|
local-messaging: app,kafka @ local
|
|
local-messaging-outbox: app,db,kafka @ local
|
|
local-notification-ingest: app,db,db-migrate-capabilities,db-promote-capabilities,keycloak,notification-smoke @ local
|
|
local-notification-serving: app,db,db-migrate-capabilities,db-promote-capabilities,keycloak,mailpit,notification-smoke @ local
|
|
local-notification-handoff: app,db,db-migrate-capabilities,db-promote-capabilities,keycloak,mailpit,notification-smoke @ local
|
|
local-graphql: app,auth-smoke,graphql-smoke,keycloak @ local
|
|
shared-infra-local: app,auth-smoke,db,keycloak,minio,minio-init,object-storage-smoke @ local
|
|
shared-infra-dev: app,auth-smoke,db,keycloak,minio,minio-init,object-storage-smoke @ dev
|
|
prod-smoke: app,auth-smoke,db,keycloak,minio,minio-init,object-storage-smoke @ prod
|
|
all-adapters: app,auth-smoke,db,db-migrate-capabilities,db-promote-capabilities,graphql-smoke,kafka,keycloak,mailpit,mongo,mongo-rs-init,notification-smoke @ local
|
|
|
|
PORT RESULT: all 15 lanes match src/config/runtime/compose-profile-contracts.json (exit 0)
|
|
|
|
이식본이 검사한 것: ①compose 버전 하한 ②레인별 정확한 서비스 집합 ③렌더된
|
|
SPRING_PROFILES_ACTIVE ④병합 모델의 마운트 대상 중복 ⑤서비스-역할 분할과 --wait 대상 정합
|
|
⑥Keycloak realm 의 밑줄 키. 원본 스크립트와 같은 순서·같은 판정 기준이다.
|
|
|
|
주의: 이것은 두 번째 의견이다. test 자신의 주석이 경계하는 바로 그것 —
|
|
"a test that re-derived the same checks in Java would be a second opinion that can agree with the
|
|
contract while the script disagrees" — 이므로, jq 가 있는 기계에서 원본 스크립트로 확인하는 것이
|
|
여전히 정본이다. 여기서 얻은 것은 "계약 위반의 증거가 없다"이지 "스크립트가 통과한다"가 아니다.
|
|
|
|
────────────────────────────────────────────────────────────────────────
|
|
5. 이 재실행에서 나온 finding
|
|
────────────────────────────────────────────────────────────────────────
|
|
P3 · app-bootstrap — everyLaneMatchesItsContract 가 스크립트의 전제 결손(exit 78)과
|
|
계약 위반(exit 1)을 구분하지 않는다. docker compose 만 assumption 으로 막혀 있고
|
|
jq·계약 파일 가독성은 막혀 있지 않다. 확인하지 못한 것이 틀린 것으로 보고된다.
|
|
P3 · fileserver — 한글 파일명을 쓰는 test 가 UTF-8 sun.jnu.encoding 을 전제하면서
|
|
그 전제를 test JVM 에 고정하지도, assumption 으로 선언하지도 않는다.
|
|
|
|
애플리케이션 소스는 변경하지 않았다. jq 도 설치하지 않았다.
|