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 도 설치하지 않았다.